neon/
lifecycle.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//! # Environment life cycle APIs
//!
//! These APIs map to the life cycle of a specific "Agent" or self-contained
//! environment. If a Neon module is loaded multiple times (Web Workers, worker
//! threads), these API will be handle data associated with a specific instance.
//!
//! See the [N-API Lifecycle][npai-docs] documentation for more details.
//!
//! [napi-docs]: https://nodejs.org/api/n-api.html#n_api_environment_life_cycle_apis

use std::{
    any::Any,
    marker::PhantomData,
    sync::{
        atomic::{AtomicU32, Ordering},
        Arc,
    },
};

use crate::{
    context::Context,
    event::Channel,
    handle::root::NapiRef,
    sys::{lifecycle, raw::Env, tsfn::ThreadsafeFunction},
    types::promise::NodeApiDeferred,
};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
/// Uniquely identifies an instance of the module
///
/// _Note_: Since `InstanceData` is created lazily, the order of `id` may not
/// reflect the order that instances were created.
pub(crate) struct InstanceId(u32);

impl InstanceId {
    fn next() -> Self {
        static NEXT_ID: AtomicU32 = AtomicU32::new(0);

        let next = NEXT_ID.fetch_add(1, Ordering::SeqCst).checked_add(1);
        match next {
            Some(id) => Self(id),
            None => panic!("u32 overflow ocurred in Lifecycle InstanceId"),
        }
    }
}

/// `InstanceData` holds Neon data associated with a particular instance of a
/// native module. If a module is loaded multiple times (e.g., worker threads), this
/// data will be unique per instance.
pub(crate) struct InstanceData {
    id: InstanceId,

    /// Used to free `Root` in the same JavaScript environment that created it
    ///
    /// _Design Note_: An `Arc` ensures the `ThreadsafeFunction` outlives the unloading
    /// of a module. Since it is unlikely that modules will be re-loaded frequently, this
    /// could be replaced with a leaked `&'static ThreadsafeFunction<NapiRef>`. However,
    /// given the cost of FFI, this optimization is omitted until the cost of an
    /// `Arc` is demonstrated as significant.
    drop_queue: Arc<ThreadsafeFunction<DropData>>,

    /// Shared `Channel` that is cloned to be returned by the `cx.channel()` method
    shared_channel: Channel,

    /// Table of user-defined instance-local cells.
    locals: LocalTable,
}

#[derive(Default)]
pub(crate) struct LocalTable {
    cells: Vec<LocalCell>,
}

pub(crate) type LocalCellValue = Box<dyn Any + Send + 'static>;

#[derive(Default)]
pub(crate) enum LocalCell {
    #[default]
    /// Uninitialized state.
    Uninit,
    /// Intermediate "dirty" state representing the middle of a `get_or_try_init` transaction.
    Trying,
    /// Fully initialized state.
    Init(LocalCellValue),
}

impl LocalCell {
    /// Establish the initial state at the beginning of the initialization protocol.
    /// This method ensures that re-entrant initialization always panics (i.e. when
    /// an existing `get_or_try_init` is in progress).
    fn pre_init<F>(&mut self, f: F)
    where
        F: FnOnce() -> LocalCell,
    {
        match self {
            LocalCell::Uninit => {
                *self = f();
            }
            LocalCell::Trying => panic!("attempt to reinitialize Local during initialization"),
            LocalCell::Init(_) => {}
        }
    }

    pub(crate) fn get<'cx, 'a, C>(cx: &'a mut C, id: usize) -> Option<&'a mut LocalCellValue>
    where
        C: Context<'cx>,
    {
        let cell = InstanceData::locals(cx).get(id);
        match cell {
            LocalCell::Init(ref mut b) => Some(b),
            _ => None,
        }
    }

    pub(crate) fn get_or_init<'cx, 'a, C, F>(
        cx: &'a mut C,
        id: usize,
        f: F,
    ) -> &'a mut LocalCellValue
    where
        C: Context<'cx>,
        F: FnOnce() -> LocalCellValue,
    {
        InstanceData::locals(cx)
            .get(id)
            .pre_init(|| LocalCell::Init(f()));

        LocalCell::get(cx, id).unwrap()
    }

    pub(crate) fn get_or_try_init<'cx, 'a, C, E, F>(
        cx: &'a mut C,
        id: usize,
        f: F,
    ) -> Result<&'a mut LocalCellValue, E>
    where
        C: Context<'cx>,
        F: FnOnce(&mut C) -> Result<LocalCellValue, E>,
    {
        // Kick off a new transaction and drop it before getting the result.
        {
            let mut tx = TryInitTransaction::new(cx, id);
            tx.run(|cx| f(cx))?;
        }

        // If we're here, the transaction has succeeded, so get the result.
        Ok(LocalCell::get(cx, id).unwrap())
    }
}

impl LocalTable {
    pub(crate) fn get(&mut self, index: usize) -> &mut LocalCell {
        if index >= self.cells.len() {
            self.cells.resize_with(index + 1, Default::default);
        }
        &mut self.cells[index]
    }
}

/// An RAII implementation of `LocalCell::get_or_try_init`, which ensures that
/// the state of a cell is properly managed through all possible control paths.
/// As soon as the transaction begins, the cell is labelled as being in a dirty
/// state (`LocalCell::Trying`), so that any additional re-entrant attempts to
/// initialize the cell will fail fast. The `Drop` implementation ensures that
/// after the transaction, the cell goes back to a clean state of either
/// `LocalCell::Uninit` if it fails or `LocalCell::Init` if it succeeds.
struct TryInitTransaction<'cx, 'a, C: Context<'cx>> {
    cx: &'a mut C,
    id: usize,
    _lifetime: PhantomData<&'cx ()>,
}

impl<'cx, 'a, C: Context<'cx>> TryInitTransaction<'cx, 'a, C> {
    fn new(cx: &'a mut C, id: usize) -> Self {
        let mut tx = Self {
            cx,
            id,
            _lifetime: PhantomData,
        };
        tx.cell().pre_init(|| LocalCell::Trying);
        tx
    }

    /// _Post-condition:_ If this method returns an `Ok` result, the cell is in the
    /// `LocalCell::Init` state.
    fn run<E, F>(&mut self, f: F) -> Result<(), E>
    where
        F: FnOnce(&mut C) -> Result<LocalCellValue, E>,
    {
        if self.is_trying() {
            let value = f(self.cx)?;
            *self.cell() = LocalCell::Init(value);
        }
        Ok(())
    }

    fn cell(&mut self) -> &mut LocalCell {
        InstanceData::locals(self.cx).get(self.id)
    }

    #[allow(clippy::wrong_self_convention)]
    fn is_trying(&mut self) -> bool {
        matches!(self.cell(), LocalCell::Trying)
    }
}

impl<'cx, 'a, C: Context<'cx>> Drop for TryInitTransaction<'cx, 'a, C> {
    fn drop(&mut self) {
        if self.is_trying() {
            *self.cell() = LocalCell::Uninit;
        }
    }
}

/// Wrapper for raw Node-API values to be dropped on the main thread
pub(crate) enum DropData {
    Deferred(NodeApiDeferred),
    Ref(NapiRef),
}

impl DropData {
    /// Drop a value on the main thread
    fn drop(env: Option<Env>, data: Self) {
        if let Some(env) = env {
            unsafe {
                match data {
                    DropData::Deferred(data) => data.leaked(env),
                    DropData::Ref(data) => data.unref(env),
                }
            }
        }
    }
}

impl InstanceData {
    /// Return the data associated with this module instance, lazily initializing if
    /// necessary.
    ///
    /// # Safety
    /// No additional locking (e.g., `Mutex`) is necessary because holding a
    /// `Context` reference ensures serialized access.
    pub(crate) fn get<'cx, C: Context<'cx>>(cx: &mut C) -> &mut InstanceData {
        let env = cx.env().to_raw();
        let data = unsafe { lifecycle::get_instance_data::<InstanceData>(env).as_mut() };

        if let Some(data) = data {
            return data;
        }

        let drop_queue = unsafe {
            let queue = ThreadsafeFunction::new(env, DropData::drop);
            queue.unref(env);
            queue
        };

        let shared_channel = {
            let mut channel = Channel::new(cx);
            channel.unref(cx);
            channel
        };

        let data = InstanceData {
            id: InstanceId::next(),
            drop_queue: Arc::new(drop_queue),
            shared_channel,
            locals: LocalTable::default(),
        };

        unsafe { &mut *lifecycle::set_instance_data(env, data) }
    }

    /// Helper to return a reference to the `drop_queue` field of `InstanceData`
    pub(crate) fn drop_queue<'cx, C: Context<'cx>>(
        cx: &mut C,
    ) -> Arc<ThreadsafeFunction<DropData>> {
        Arc::clone(&InstanceData::get(cx).drop_queue)
    }

    /// Clones the shared channel and references it since new channels should start
    /// referenced, but the shared channel is unreferenced.
    pub(crate) fn channel<'cx, C: Context<'cx>>(cx: &mut C) -> Channel {
        let mut channel = InstanceData::get(cx).shared_channel.clone();
        channel.reference(cx);
        channel
    }

    /// Unique identifier for this instance of the module
    pub(crate) fn id<'cx, C: Context<'cx>>(cx: &mut C) -> InstanceId {
        InstanceData::get(cx).id
    }

    /// Helper to return a reference to the `locals` field of `InstanceData`.
    pub(crate) fn locals<'cx, C: Context<'cx>>(cx: &mut C) -> &mut LocalTable {
        &mut InstanceData::get(cx).locals
    }
}