neon/thread/
mod.rs

1//! Thread-local storage for JavaScript threads.
2//!
3//! At runtime, an instance of a Node.js addon can contain its own local storage,
4//! which can then be shared and accessed as needed from Rust in a Neon module. This can
5//! be useful for setting up long-lived state that needs to be shared between calls
6//! of an addon's APIs.
7//!
8//! For example, an addon may wish to track the [thread ID][threadId] of each of its
9//! instances:
10//!
11//! ```
12//! # use neon::prelude::*;
13//! # use neon::thread::LocalKey;
14//! static THREAD_ID: LocalKey<u32> = LocalKey::new();
15//!
16//! pub fn thread_id(cx: &mut Cx) -> NeonResult<u32> {
17//!     THREAD_ID.get_or_try_init(cx, |cx| {
18//!         let require: Handle<JsFunction> = cx.global("require")?;
19//!         let worker: Handle<JsObject> = require
20//!             .bind(cx)
21//!             .arg("node:worker_threads")?
22//!             .call()?;
23//!         let thread_id: f64 = worker.prop(cx, "threadId").get()?;
24//!         Ok(thread_id as u32)
25//!     }).cloned()
26//! }
27//! ```
28//!
29//! ### The Addon Lifecycle
30//!
31//! For some use cases, a single shared global constant stored in a `static` variable
32//! might be sufficient:
33//!
34//! ```
35//! static MY_CONSTANT: &'static str = "hello Neon";
36//! ```
37//!
38//! This variable will be allocated when the addon is first loaded into the Node.js
39//! process. This works fine for single-threaded applications, or global thread-safe
40//! data.
41//!
42//! However, since the addition of [worker threads][workers] in Node v10,
43//! modules can be instantiated multiple times in a single Node process. So even
44//! though the dynamically-loaded binary library (i.e., the Rust implementation of
45//! the addon) is only loaded once in the running process, its [`#[main]`](crate::main)
46//! function can be executed multiple times with distinct module objects, one per application
47//! thread:
48//!
49//! ![The Node.js addon lifecycle, described in detail below.][lifecycle]
50//!
51//! This means that any thread-local data needs to be initialized separately for each
52//! instance of the addon. This module provides a simple container type, [`LocalKey`],
53//! for allocating and initializing thread-local data. (Technically, this data is stored in the
54//! addon's [module instance][environment], which is equivalent to being thread-local.)
55//!
56//! A common example is when an addon needs to maintain a reference to a JavaScript value. A
57//! reference can be [rooted](crate::handle::Root) and stored in a static, but references cannot
58//! be used across separate threads. By placing the reference in thread-local storage, an
59//! addon can ensure that each thread stores its own distinct reference:
60//!
61//! ```
62//! # use neon::prelude::*;
63//! # use neon::thread::LocalKey;
64//! # fn initialize_my_datatype<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> { unimplemented!() }
65//! static MY_CONSTRUCTOR: LocalKey<Root<JsFunction>> = LocalKey::new();
66//!
67//! pub fn my_constructor<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> {
68//!     let constructor = MY_CONSTRUCTOR.get_or_try_init(cx, |cx| {
69//!         let constructor: Handle<JsFunction> = initialize_my_datatype(cx)?;
70//!         Ok(constructor.root(cx))
71//!     })?;
72//!     Ok(constructor.to_inner(cx))
73//! }
74//! ```
75//!
76//! Notice that if this code were implemented without a `LocalKey`, it would panic whenever
77//! one thread stores an instance of the constructor and a different thread attempts to
78//! access it with the call to [`to_inner()`](crate::handle::Root::to_inner).
79//!
80//! ### When to Use Thread-Local Storage
81//!
82//! Single-threaded applications don't generally need to worry about thread-local data.
83//! There are two cases where Neon apps should consider storing static data in a
84//! `LocalKey` storage cell:
85//!
86//! - **Multi-threaded applications:** If your Node application uses the `Worker`
87//!   API, you'll want to store any static data that might get access from multiple
88//!   threads in thread-local data.
89//! - **Libraries:** If your addon is part of a library that could be used by multiple
90//!   applications, you'll want to store static data in thread-local data in case the
91//!   addon ends up instantiated by multiple threads in some future application.
92//!
93//! ### Why Not Use Standard TLS?
94//!
95//! Since the JavaScript engine may not tie JavaScript threads 1:1 to system threads,
96//! it is recommended to use this module instead of the Rust standard thread-local storage
97//! when associating data with a JavaScript thread.
98//!
99//! [environment]: https://nodejs.org/api/n-api.html#environment-life-cycle-apis
100//! [lifecycle]: https://raw.githubusercontent.com/neon-bindings/neon/main/doc/lifecycle.png
101//! [workers]: https://nodejs.org/api/worker_threads.html
102//! [threadId]: https://nodejs.org/api/worker_threads.html#workerthreadid
103
104use std::any::Any;
105use std::marker::PhantomData;
106use std::sync::atomic::{AtomicUsize, Ordering};
107
108use once_cell::sync::OnceCell;
109
110use crate::context::Context;
111use crate::lifecycle::LocalCell;
112
113static COUNTER: AtomicUsize = AtomicUsize::new(0);
114
115fn next_id() -> usize {
116    COUNTER.fetch_add(1, Ordering::SeqCst)
117}
118
119/// A JavaScript thread-local container that owns its contents, similar to
120/// [`std::thread::LocalKey`] but tied to a JavaScript thread rather
121/// than a system thread.
122///
123/// ### Initialization and Destruction
124///
125/// Initialization is dynamically performed on the first call to one of the `init` methods
126/// of `LocalKey`, and values that implement [`Drop`] get destructed when
127/// the JavaScript thread exits, i.e. when a worker thread terminates or the main thread
128/// terminates on process exit.
129#[derive(Default)]
130pub struct LocalKey<T> {
131    _type: PhantomData<T>,
132    id: OnceCell<usize>,
133}
134
135impl<T> LocalKey<T> {
136    /// Creates a new local value. This method is `const`, so it can be assigned to
137    /// static variables.
138    pub const fn new() -> Self {
139        Self {
140            _type: PhantomData,
141            id: OnceCell::new(),
142        }
143    }
144
145    fn id(&self) -> usize {
146        *self.id.get_or_init(next_id)
147    }
148}
149
150impl<T: Any + Send + 'static> LocalKey<T> {
151    /// Gets the current value of the cell. Returns `None` if the cell has not
152    /// yet been initialized.
153    pub fn get<'cx, 'a, C>(&self, cx: &'a mut C) -> Option<&'cx T>
154    where
155        C: Context<'cx>,
156    {
157        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
158        // id guarantees that the cell is only ever assigned instances of type T.
159        let r: Option<&T> =
160            LocalCell::get(cx, self.id()).map(|value| value.downcast_ref().unwrap());
161
162        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
163        // move or change for the duration of the context.
164        unsafe { std::mem::transmute::<Option<&'a T>, Option<&'cx T>>(r) }
165    }
166
167    /// Gets the current value of the cell, initializing it with the result of
168    /// calling `f` if it has not yet been initialized.
169    pub fn get_or_init<'cx, 'a, C, F>(&self, cx: &'a mut C, f: F) -> &'cx T
170    where
171        C: Context<'cx>,
172        F: FnOnce() -> T,
173    {
174        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
175        // id guarantees that the cell is only ever assigned instances of type T.
176        let r: &T = LocalCell::get_or_init(cx, self.id(), || Box::new(f()))
177            .downcast_ref()
178            .unwrap();
179
180        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
181        // move or change for the duration of the context.
182        unsafe { std::mem::transmute::<&'a T, &'cx T>(r) }
183    }
184
185    /// Gets the current value of the cell, initializing it with the result of
186    /// calling `f` if it has not yet been initialized. Returns `Err` if the
187    /// callback triggers a JavaScript exception.
188    ///
189    /// # Panics
190    ///
191    /// During the execution of `f`, calling any methods on this `LocalKey` that
192    /// attempt to initialize it will panic.
193    pub fn get_or_try_init<'cx, 'a, C, E, F>(&self, cx: &'a mut C, f: F) -> Result<&'cx T, E>
194    where
195        C: Context<'cx>,
196        F: FnOnce(&mut C) -> Result<T, E>,
197    {
198        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
199        // id guarantees that the cell is only ever assigned instances of type T.
200        let r: &T = LocalCell::get_or_try_init(cx, self.id(), |cx| Ok(Box::new(f(cx)?)))?
201            .downcast_ref()
202            .unwrap();
203
204        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
205        // move or change for the duration of the context.
206        Ok(unsafe { std::mem::transmute::<&'a T, &'cx T>(r) })
207    }
208}
209
210impl<T: Any + Send + Default + 'static> LocalKey<T> {
211    /// Gets the current value of the cell, initializing it with the default value
212    /// if it has not yet been initialized.
213    pub fn get_or_init_default<'cx, 'a, C>(&self, cx: &'a mut C) -> &'cx T
214    where
215        C: Context<'cx>,
216    {
217        self.get_or_init(cx, Default::default)
218    }
219}