napi-6
only.Expand description
Thread-local storage for JavaScript threads.
At runtime, an instance of a Node.js addon can contain its own local storage, which can then be shared and accessed as needed from Rust in a Neon module. This can be useful for setting up long-lived state that needs to be shared between calls of an addon’s APIs.
For example, an addon may wish to track the thread ID of each of its instances:
static THREAD_ID: LocalKey<u32> = LocalKey::new();
pub fn thread_id(cx: &mut Cx) -> NeonResult<u32> {
THREAD_ID.get_or_try_init(cx, |cx| {
let require: Handle<JsFunction> = cx.global("require")?;
let worker: Handle<JsObject> = require
.bind(cx)
.arg("node:worker_threads")?
.call()?;
let thread_id: f64 = worker.prop(cx, "threadId").get()?;
Ok(thread_id as u32)
}).cloned()
}
§The Addon Lifecycle
For some use cases, a single shared global constant stored in a static
variable
might be sufficient:
static MY_CONSTANT: &'static str = "hello Neon";
This variable will be allocated when the addon is first loaded into the Node.js process. This works fine for single-threaded applications, or global thread-safe data.
However, since the addition of worker threads in Node v10,
modules can be instantiated multiple times in a single Node process. So even
though the dynamically-loaded binary library (i.e., the Rust implementation of
the addon) is only loaded once in the running process, its #[main]
function can be executed multiple times with distinct module objects, one per application
thread:
This means that any thread-local data needs to be initialized separately for each
instance of the addon. This module provides a simple container type, LocalKey
,
for allocating and initializing thread-local data. (Technically, this data is stored in the
addon’s module instance, which is equivalent to being thread-local.)
A common example is when an addon needs to maintain a reference to a JavaScript value. A reference can be rooted and stored in a static, but references cannot be used across separate threads. By placing the reference in thread-local storage, an addon can ensure that each thread stores its own distinct reference:
static MY_CONSTRUCTOR: LocalKey<Root<JsFunction>> = LocalKey::new();
pub fn my_constructor<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> {
let constructor = MY_CONSTRUCTOR.get_or_try_init(cx, |cx| {
let constructor: Handle<JsFunction> = initialize_my_datatype(cx)?;
Ok(constructor.root(cx))
})?;
Ok(constructor.to_inner(cx))
}
Notice that if this code were implemented without a LocalKey
, it would panic whenever
one thread stores an instance of the constructor and a different thread attempts to
access it with the call to to_inner()
.
§When to Use Thread-Local Storage
Single-threaded applications don’t generally need to worry about thread-local data.
There are two cases where Neon apps should consider storing static data in a
LocalKey
storage cell:
- Multi-threaded applications: If your Node application uses the
Worker
API, you’ll want to store any static data that might get access from multiple threads in thread-local data. - Libraries: If your addon is part of a library that could be used by multiple applications, you’ll want to store static data in thread-local data in case the addon ends up instantiated by multiple threads in some future application.
§Why Not Use Standard TLS?
Since the JavaScript engine may not tie JavaScript threads 1:1 to system threads, it is recommended to use this module instead of the Rust standard thread-local storage when associating data with a JavaScript thread.
Structs§
- A JavaScript thread-local container that owns its contents, similar to
std::thread::LocalKey
but tied to a JavaScript thread rather than a system thread.