SpiderMonkey Internals: Thread Safety

  • Revision slug: SpiderMonkey/Internals/Thread_Safety
  • Revision title: SpiderMonkey Internals: Thread Safety
  • Revision id: 31891
  • Created:
  • Creator: Brendan
  • Is current revision? No
  • Comment /* Making property accesses thread-safe */

Revision Content

This page describes implementation details of the SpiderMonkey JavaScript engine. It is mainly of interest to people working on SpiderMonkey itself. See JS_THREADSAFE for a gentler introduction to using SpiderMonkey in a multi-threaded application.

General background

SpiderMonkey has a top-level struct, JSRuntime, that handles, among other things, memory management and "global" data structures. A program typically has only one JSRuntime, even if it has many threads. The JSRuntime is the universe in which JS objects live; they can't travel to other JSRuntimes.

All JS code and most JSAPI calls run within a JSContext. The JSContext is a child of the JSRuntime; exception handling, for example, is per-JSContext. Each JSContext must be used by only one thread at a time.

Objects may be shared among JSContexts within a JSRuntime. There's no fixed association between an object and a context.

Thread-safety in SpiderMonkey is turned on by compiling with -DJS_THREADSAFE. In a JS_THREADSAFE build, these operations are handled specially:

  • access to JSRuntime data structures
  • garbage collection
  • access to properties of objects

Accesses to JSRuntime data structures are serialized with a few mutexes. The treatment of GC and properties requires more explanation.

Making GC thread-safe

With JS_THREADSAFE, the API changes slightly. The program must group JSAPI calls into "requests":

   JS_SetContextThread(cx);
   JS_BeginRequest(cx);
   /* ... do stuff ... */
   JS_EndRequest(cx);
   JS_ClearContextThread(cx);

It isn't a bottleneck; multiple threads are allowed to be in requests on the same JSRuntime at once. See JS_BeginRequest.

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.

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.

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 JSRuntime-wide GC allocator while it's in the lock.

Making property accesses thread-safe

To the JSAPI user, all property accesses appear to be serialized. (Note: The JSAPI has never explicitly made quite such a strong promise. This is true today as far as the author is aware but even if so, it will soon stop being true. In SpiderMonkey 1.8, programs using JS_SetPrototype on SMP hardware could trigger cases where different threads observe the set-prototype operation to happen at inconsistent times.) The scheme described below is an optimization, internal to SpiderMonkey and invisible to the user.

SpiderMonkey implicitly locks mutable objects as needed. The locking protocol is cleverly optimized. It's not a simple mutex.

Each mutable object is either "owned" by a JSContext, meaning that context may access its properties without locking; or "shared" across all JSContexts in the JSRuntime. (Again, to the end user, all objects are shared--this "ownership" is a transparent optimization.)

Initially an object is owned by the JSContext in which it was created. Locking is never needed until some other JSContext tries to access the object. At that point, we acquire a JSRuntime-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.{{ mediawiki.external('*') }} And even though the calling thread must lock, it can still avoid a costly rendezvous{{ mediawiki.external('**') }} 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)->title.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)->title.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.


{{ mediawiki.external('*') }} 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.

{{ mediawiki.external('**') }} 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 ClaimTitle in jslock.c.

Patent

The SpiderMonkey request model is patented: http://www.wipo.int/pctdb/en/wo.jsp?wo=2003042845

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

{{ languages( { "ja": "ja/SpiderMonkey_Internals/Thread_Safety" } ) }}

Revision Source

<p>This page describes implementation details of the <a href="en/SpiderMonkey">SpiderMonkey</a> JavaScript engine. It is mainly of interest to people working on SpiderMonkey itself. See <code><a href="en/JS_THREADSAFE">JS_THREADSAFE</a></code> for a gentler introduction to using SpiderMonkey in a multi-threaded application.
</p>
<h4 name="General_background"> General background </h4>
<p>SpiderMonkey has a top-level struct, <code><a href="en/JSRuntime">JSRuntime</a></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.
</p><p>All JS code and most JSAPI calls run within a <code><a href="en/JSContext">JSContext</a></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.
</p><p>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.
</p><p>Thread-safety in SpiderMonkey is turned on by compiling with <code>-DJS_THREADSAFE</code>. In a <code><a href="en/JS_THREADSAFE">JS_THREADSAFE</a></code> build, these operations are handled specially:
</p>
<ul><li> access to <code>JSRuntime</code> data structures
</li><li> garbage collection
</li><li> access to properties of objects
</li></ul>
<p>Accesses to <code>JSRuntime</code> data structures are serialized with a few mutexes. The treatment of GC and properties requires more explanation.
</p>
<h4 name="Making_GC_thread-safe"> Making GC thread-safe </h4>
<p>With <code>JS_THREADSAFE</code>, the API changes slightly. The program must group JSAPI calls into "requests":
</p>
<pre class="eval">   <a href="en/JS_SetContextThread">JS_SetContextThread</a>(cx);
   <a href="en/JS_BeginRequest">JS_BeginRequest</a>(cx);
   /* ... do stuff ... */
   <a href="en/JS_EndRequest">JS_EndRequest</a>(cx);
   <a href="en/JS_ClearContextThread">JS_ClearContextThread</a>(cx);
</pre>
<p>It isn't a bottleneck; multiple threads are allowed to be in requests on the same <code>JSRuntime</code> at once. See <a href="en/JS_BeginRequest">JS_BeginRequest</a>.
</p><p>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.
</p><p>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.
</p><p>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.
</p>
<h4 name="Making_property_accesses_thread-safe"> Making property accesses thread-safe </h4>
<p>To the JSAPI user, all property accesses appear to be serialized. <i>(Note: The JSAPI has never explicitly made quite such a strong promise. This is true today as far as the author is aware but even if so, it will soon stop being true. In SpiderMonkey 1.8, programs using <code><a href="en/JS_SetPrototype">JS_SetPrototype</a></code> on SMP hardware could trigger cases where different threads observe the set-prototype operation to happen at inconsistent times.)</i> The scheme described below is an optimization, internal to SpiderMonkey and invisible to the user.
</p><p>SpiderMonkey implicitly locks mutable objects as needed. The locking protocol is cleverly optimized. It's not a simple mutex.
</p><p>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.)
</p><p>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.{{ mediawiki.external('*') }} And even though the calling thread must lock, it can still avoid a costly rendezvous{{ mediawiki.external('**') }} with another thread, if the context that owns the object is not currently in a request.
</p><p>I found it helpful to read the code for <code><a class="external" href="http://lxr.mozilla.org/seamonkey/ident?i=OBJ_GET_SLOT">OBJ_GET_SLOT</a></code>, defined in <a class="external" href="http://hg.mozilla.org/mozilla-central/?file/tip/js/src/jsobj.h">jsobj.h</a>, and track down the various things it calls.
</p>
<pre class="eval">   /* 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) &amp;&amp; OBJ_SCOPE(obj)-&gt;title.ownercx == cx)      \
        ? LOCKED_OBJ_GET_SLOT(obj, slot)                                 \
        : js_GetSlotThreadSafe(cx, obj, slot))
</pre>
<p>Here <code>OBJ_CHECK_SLOT()</code> is just an assertion. <code>LOCKED_OBJ_GET_SLOT()</code> is the fast path; it amounts to an array access. <code>OBJ_SCOPE(obj)-&gt;title.ownercx</code> is the object's owning context, or <code>NULL</code> if the object is "shared". (An <code>OBJ_SCOPE</code> 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.)
</p><p>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.
</p><p>In short, any <code>JSContext</code> 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.
</p>
<hr>
<p>{{ mediawiki.external('*') }} 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 <code>__proto__</code> 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.
</p><p>{{ mediawiki.external('**') }} 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 <a class="external" href="http://lxr.mozilla.org/seamonkey/ident?i=ClaimTitle">ClaimTitle</a> in jslock.c.
</p>
<h4 name="Patent"> Patent </h4>
<p>The SpiderMonkey request model is patented: <a class=" external" href="http://www.wipo.int/pctdb/en/wo.jsp?wo=2003042845" rel="freelink">http://www.wipo.int/pctdb/en/wo.jsp?wo=2003042845</a>
</p><p>The Mozilla Public License in the SpiderMonkey source code grants a worldwide royalty-free license to this invention.
</p>
<div class="noinclude">
</div>
{{ languages( { "ja": "ja/SpiderMonkey_Internals/Thread_Safety" } ) }}
Revert to this revision