マルチスレッド化で見えてきたもの

SpiderMonkeyでは、1つのJSランタイム(JSRuntime*)に対して複数のJSコンテキスト(JSContext*)をぶら下げることができるので、一般的にマルチスレッドでの動作はマルチJSコンテキストでの実装が想定されていると思われる。


例えば以下のようなコードを想定する。


static JSGCCallback prevGCCallback = NULL;

...

// 独自のGCコールバック処理
JSBool newGCCallback(JSContext *cx, JSGCStatus status)
{
...
return (prevCallback ? prevGCCallback(cx, status) : JS_TRUE);
}

// 新しいコンテキストの追加
JSContext *CreateNewContext(JSRuntime *rt)
{
JSContext *cx;

cx = JS_NewContext(rt, 8192);

...
prevGCCallback = JS_SetGCCallback(cx, newGCCallback);
...

return cx;
}


一見問題なさそうに思えるが、実はこのコードを実行するとGCが発生するたびにアプリが落ちる。


JS_SetGCCallbackはcxを引数に取るため、新しいコンテキストcxに対して、新しいコールバックを関連付けるように思えるが、コールバックはランタイムに対して一意である(ように見える)ため、CreateNewContextを2回以上呼び出すとprevCallbackがnewGCCallbackで上書きされてしまい、newGCCallbackの中から更にnewGCCallbackを呼び出すことになってしまう。そうして無限ネストが発生し、最終的にはスタックオーバーフローでアプリは強制終了する。


とりあえず回避のため以下のようなコードを試してみたところ、何とか動くようだ。
(自分自身が既に登録されている場合はコールバックチェインに追加しない)


tmp_prevGCCallback = JS_SetGCCallback(cx, newGCCallback);
if(tmp_prevGCCallback != newGCCallback){
prevGCCallback = tmp_prevGCCallback;
}


js32.dllのソースコードを調べてみたら、案の定以下のような実装であった。


JS_PUBLIC_API(JSGCCallback)
JS_SetGCCallback(JSContext *cx, JSGCCallback cb)
{
return JS_SetGCCallbackRT(cx->runtime, cb);
}
...つまり、同機能のランタイム版API(JS_SetGCCallbackRT)が存在して、それにスルーされてるだけ。
こんなんだったらわかりにくいんだから、最初からJS_SetGCCallbackRTの方だけを外部I/Fにしてくれよ、って感じだ。それとも将来的にはコンテキスト別のコールバックも実装される(現時点では制限)ということなのか。。。?