Thread Safety

This page describes implementation details of the SpiderMonkey JavaScript engine. It is mainly of interest to people working on SpiderMonkey itself, but this information is also helpful for anyone embedding SpiderMonkey in a multithreaded environment. See also JS_THREADSAFE.

General background

SpiderMonkeyは、最上位の構造体としてJSRuntimeを利用します。これらの構造体は、メモリの管理とグローバルなデータ構造を扱います。 通常の場合プログラムは、多くのスレッドを利用する場合でも、1つだけJSRuntimeを使います。JSRuntimeは、JSオブジェクトが動作する 世界といってもよいでしょう。オブジェクトは、他のJSRuntimeに移って動作することはできません。 SpiderMonkey has a top-level struct, <code></code>, that handles, among other things, memory management and "global" data structures. A program typically has only one <code>JSRuntime</code>, even if it has many threads. The <code>JSRuntime</code> is the universe in which JS objects live; they can't travel to other <code>JSRuntime</code>s.

全てのJSコードとほとんどのJSAPIの呼び出しは、JSContextの中で動作します。JSContextは、 JSRuntimeの子供のようなもので、例えば、例外の処理などは、JSContextごとに実行されます。 各JSContextは、同時に複数スレッドからアクセスしてはなりません。 All JS code and most JSAPI calls run within a <code>JSContext</code>. The <code>JSContext</code> is a child of the <code>JSRuntime</code>; exception handling, for example, is per-<code>JSContext</code>. Each <code>JSContext</code> must be used by only one thread at a time.

オブジェクトは、同じJSRuntime内のJSContext間で共有できます。コンテキストとオブジェクトの間には、固定的な関係はありません。 Objects may be shared among <code>JSContext</code>s within a <code>JSRuntime</code>. There's no fixed association between an object and a context.

SpiderMonkeyにおけるスレッドセーフ機能は、-DJS_THREADSAFEをつけてコンパイルすることで有効になります。JS_THREADSAFEを有効にしたビルドでは、次のような操作について特別な処理が行われます。 Thread-safety in SpiderMonkey is turned on by compiling with <code>-DJS_THREADSAFE</code>. In a <code>JS_THREADSAFE</code> build, these operations are handled specially:

  • JSRuntimeのデータ構造にアクセスする場合
  • ガーベジコレクションを行う場合
  • オブジェクトのプロパティにアクセスする場合

* access to <code>JSRuntime</code> data structures * garbage collection * access to properties of objects

JSRuntimeのデータ構造へのアクセスは、mutexによってシリアライズされます。 GCとプロパティに関する処理については、もう少し詳しく説明します。 Accesses to <code>JSRuntime</code> data structures are serialized with a few mutexes. The treatment of GC and properties requires more explanation.

Making GC thread-safe

JS_THREADSAFEを用いるときは、APIを若干変更します。プログラム中でJSAPIを呼び出すときは、次に示す"request"で くくらなくてはなりません。 With <code>JS_THREADSAFE</code>, the API changes slightly. The program must group JSAPI calls into "requests":

   /* ... do stuff ... */

複数のスレッドがrequest内で同じJSRuntimeを同時にアクセスできるため、この操作がボトルネックになることはありません。詳細はJS_BeginRequestを参照してください。 It isn't a bottleneck; multiple threads are allowed to be in requests on the same <code>JSRuntime</code> at once. See JS_BeginRequest.

requestの最も顕著な効果は、いつでも複数のスレッドがrequestのくくりを実行することができるか、1つのスレッドだけがGCを実行していて他のスレッドが停止させられている 用にすることです。JS_GC()を呼び出しても、他のスレッドが停止させられるまでは、処理がブロックされます。つまり、他のスレッドがJSAPIを呼び出していないか (呼び出していないときには、特に注意する必要がないので)、JSAPIを実行中であってもGCが終了するのを待っているブロックしている状態になるまで、 GCの実行は停止させられます。 The most obvious effect of a request is: at any given moment there can either be multiple threads in active requests, or one thread doing GC and all requests suspended. A call to JS_GC() will block until the latter becomes possible. In other words, GC waits until each other thread is either outside JSAPI (in which case we don't care what it's doing) or else in JSAPI, but blocked, waiting for GC to finish.

requestのくくりの中にいないときには、スレッドは、GCに影響を与えるような処理を行ってはいけません。 当たり前のことですが、requestのくくりの中では、GCが抑止されてしまうので、ブロックしたり、時間のかかる処理を行ってはいけません。 Threads must not do anything that would affect GC while outside a request. And obviously you shouldn't block or otherwise dilly-dally while in a request; it prohibits GC. 最適化のために、スレッドには、スレッドごとの大きさごとに分けられた、割り当て可能なGC用のメモリの集合に関するフリーリストを持っています。 このリストにより、ほとんどの場合ロックせずにメモリを割り当てることができます。スレッドがロックを必要とするのは、スレッドの対応するフリーリストが 空だった場合だけです。このようなことが怒った場合に、ロックして広域のGC割り当てを行い、<code>JSRuntime</code>から、フリーリストに領域を補充します。 <!--As an optimization, each thread has its own size-classified freelists containing chunks of GC-managed memory ready to be allocated. This allows allocation to avoid locking most of the time (a significant speed win). A thread needs to lock on allocation only when the relevant per-thread freelist is empty. When this happens, the thread also refills that freelist from the <code>JSRuntime</code>-wide GC allocator while it's in the lock.

Making property accesses thread-safe

JSAPIのユーザにとっては、プロパティへのアクセスは全てシリアライズされているように見えます。これから記述する方法は、SpiderMonkeyの内部に関するもので ユーザにとっては見えない、最適化についてです。 To the JSAPI user, all property accesses appear to be serialized. The scheme described below is an optimization, internal to SpiderMonkey and invisible to the user.

SpiderMonkeyの実装では、mutableなオブジェクトは必要に応じて暗黙のうちにロックされます。ロックの手順は、うまく最適化されていて、単なるmutexではありません。 SpiderMonkey implicitly locks mutable objects as needed. The locking protocol is cleverly optimized. It's not a simple mutex.

それぞれの、mutableなオブジェクトは、JSContextが"占有"している(つまり、コンテキストがロックをしなくてもプロパティにアクセスできる) か、JSRuntime内のJSContextで、"共有"しているかのいずれかです。(繰り返しになりますが、JSAPIのユーザにとっては、全てのオブジェクトは 共有されており、この"所有関係"は、ユーザには見えないように最適化されています。) Each mutable object is either "owned" by a <code>JSContext</code>, meaning that context may access its properties without locking; or "shared" across all <code>JSContext</code>s in the <code>JSRuntime</code>. (Again, to the end user, all objects are shared--this "ownership" is a transparent optimization.)

初期状態では、オブジェクトの所有者は、オブジェクトを作ったJSContext</code.です。他の<code>JSContextがオブジェクトにアクセスしようとするまでは、 ロックは全く必要ではありません。他のコンテキストがアクセスしようとした時点で、JSRuntimeの広域ロックを取得します。ただ、この時点においても プロパティへの通常のアクセスは、1つのオブジェクト(つまり、プロパティを持っているオブジェクトのこと)のmutable部分に触る必要があるだけです。 従って、デッドロックは問題になりません。* また、スレッドがロックする必要がある場合でも、オブジェクトを所有しているコンテキストが requestのくくりを実行していないのであれば、コストのかかる他のスレッドとのランデブ**を避けることができます。 Initially an object is owned by the <code>JSContext</code> in which it was created. Locking is never needed until some other <code>JSContext</code> tries to access the object. At that point, we acquire a <code>JSRuntime</code>-wide lock. But even then, each ordinary property access only needs to touch mutable parts of one object (the one that has the property), so deadlock isn't an issue.* And even though the calling thread must lock, it can still avoid a costly rendezvous** with another thread, if the context that owns the object is not currently in a request.

I found it helpful to read the code for OBJ_GET_SLOT, defined in jsobj.h, and track down the various things it calls.

   /* Thread-safe functions and wrapper macros for accessing slots in obj. */
   #define OBJ_GET_SLOT(cx,obj,slot)                                     \
       (OBJ_CHECK_SLOT(obj, slot),                                       \
        (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->ownercx == cx)            \
        ? LOCKED_OBJ_GET_SLOT(obj, slot)                                 \
        : js_GetSlotThreadSafe(cx, obj, slot))

Here OBJ_CHECK_SLOT() is just an assertion. LOCKED_OBJ_GET_SLOT() is the fast path; it amounts to an array access. OBJ_SCOPE(obj)->ownercx is the object's owning context, or NULL if the object is "shared". (An OBJ_SCOPE is just a handy place to stick this field; it is often shared across multiple objects, so all this locking is somewhat higher than object-level.)

This may appear unsafe, at least in SMP environments where writing a word isn't guaranteed to make the new value immediately visible to other CPUs. Requests save the day again: entering or leaving a request always briefly acquires a lock, which forces a read-write barrier. This barrier is necessary and sufficient to make several of these optimizations safe.

In short, any JSContext may touch any object, yet not only is locking usually optimized away, the threads don't even have to execute atomic instructions or barrier instructions in the most common path.

* deadlock isn't an issue: That is, SpiderMonkey doesn't need any special code to detect and avoid potential deadlock when getting or setting an ordinary property, because it can't happen--you're only locking one object at a time. Assigning to __proto__ is an example of a special case: SpiderMonkey checks for prototype chain cycles, which means locking the whole chain. So in that case, and maybe others, SpiderMonkey does extra work to avoid deadlock.

** can still avoid a costly rendezvous: That is, it can avoid "asking" that thread to surrender the object and then waiting for the thread to respond. It just takes the object. See ClaimScope in jslock.c.


The SpiderMonkey request model is patented:

The Mozilla Public License in the SpiderMonkey source code grants a worldwide royalty-free license to this invention.