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}