Note: JS_THREADSAFE is now permanently on.

JS_THREADSAFE was a compile-time option that enables support for running multiple threads of JavaScript code concurrently as long as no objects or strings are shared between them.

We have recently made major changes to this feature. Until recently, sharing objects among threads would mostly work, although scripts could easily make it crash. We have now completely removed that feature. Each thread that uses the JavaScript engine must essentially operate in a totally separate region of memory.

Requests

In a JS_THREADSAFE build, the application must separate code that uses the JSAPI from code that performs blocking I/O or time-consuming calculations.

A request is a region of code that uses the JSAPI. Requests must be bracketed with calls to JS_BeginRequest() and JS_EndRequest().

JS_BeginRequest(cx);
/* ... do JSAPI stuff ... */
JS_EndRequest(cx);

A request is always associated with a specific JSContext and runs from start to finish on a single thread.

Most JSAPI functions require the caller to be in a request. In this reference, these JSAPI functions are marked with the words "Requires request", like this:

Name Type Description
cx JSContext * The context to use. Requires request. (In a JS_THREADSAFE build, the caller must be in a request on this JSContext.)

Most JSAPI callback functions are always called from within a request. These callbacks are (unreliably!) documented with the words "Provides request", like this:

Name Type Description
cx JSContext * The context in which the event ocurred. Provides request. (In JS_THREADSAFE builds, the JS engine calls this callback only from within an active request on cx. The callback does not need to call JS_BeginRequest.)

In particular, JSNative and JSFastNative callbacks provide a request. This means that any potentially long-running operation in a native must be bracketed with calls to JS_SuspendRequest() and JS_ResumeRequest().

JSBool socket_recv(JSContext *cx, uintN argc, jsval *vp)
{
    ...
    rc = JS_SuspendRequest(cx);
    read_size = recv(socket, buf, size, flags);
    JS_ResumeRequest(cx, rc);
    ...
}

Garbage collection

Requests help make garbage collection safe when multiple threads are using the JSAPI. For each thread that is in a request:

  • Almost any call into the JSAPI may trigger garbage collection; but
  • Garbage collection does not happen at any other time (such as, for example, at the moment before the return value of JS_NewObject is assigned to a rooted variable).

These are actually the same rules that apply to single-threaded JSAPI programs. But in multithreaded programs, if you break the rules, your program is more likely to crash. This is because in single-threaded programs, a random call into the JSAPI is actually pretty unlikely to trigger GC, especially if the calling thread has not been using up a lot of memory. In a multithreaded program, even if the calling thread has been idle, other threads may be active or may call JS_GC().

The above rules mean that at any given moment, there can be either (a) multiple threads in active requests, or (b) one thread doing GC and all requests suspended. When one thread calls JS_GC or otherwise finds that garbage collection is necessary, it must wait for all other threads that are in requests to pause before garbage collection can occur. To keep this wait time to a minimum, applications must avoid long-running requests. The recommended technique in SpiderMonkey 1.8 and later is to periodically call JS_YieldRequest from an operation callback.

Sharing data among threads

"Data can be marshaled across the process boundary through a process known as smuggling." --Mr. Bunny's Guide to ActiveX

Even in JS_THREADSAFE builds, threads cannot safely share objects or strings.

Instead, data must be copied when it is sent from one thread to another. Use JS_WriteStructuredClone to transform data into a flat array of bytes that can be safely written to disk, sent to another process or even another machine, or just passed to another thread. Then use JS_ReadStructuredClone on the other side to turn the serialized data back into JavaScript objects, strings, and so on.

Sharing native functions and private data among threads

In a JS_THREADSAFE build, SpiderMonkey's internal data structures that represent JavaScript values are single-thread-only. In a DEBUG build, this is enforced with assertions.

However, SpiderMonkey does not protect the application's data structures. JSNatives and other callback functions can be called concurrently by multiple threads. Multiple threads can end up accessing private data or C/C++ global variables at the same time. It is up to the application to practice safe threading.

Sharing contexts among threads

Ordinarily, a JSContext is created, used, and destroyed by a single thread. This makes sense, as a context can only be used by one thread at a time. However, there are a few cases where an application might need to share contexts across threads. For example:

  • Many worker threads need to share a "pool" of reusable contexts, to avoid the performance cost of constantly creating and destroying contexts. (This is analogous to a database connection pool.)
  • The application has a JSContext that it needs to use each time some event happens. But the event could happen on any thread.

For such cases, use JS_ClearContextThread and JS_SetContextThread to transfer the context safely from one thread to another.

Further info

Note: SpiderMonkey Internals: Thread Safety is mostly obsolete.

Tags (2)

Edit tags

Attachments (0)

 

Attach file