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