Skip to content

Commit

Permalink
Add neon::instance::Global API for storing instance-global data.
Browse files Browse the repository at this point in the history
  • Loading branch information
dherman committed May 24, 2022
1 parent 4e75c7e commit 94111d4
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 5 deletions.
1 change: 1 addition & 0 deletions crates/neon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ nodejs-sys = "0.13.0"
libloading = "0.7.3"
semver = "0.9.0"
smallvec = "1.4.2"
once_cell = "1.10.0"
neon-macros = { version = "=0.10.1", path = "../neon-macros" }

[features]
Expand Down
72 changes: 72 additions & 0 deletions crates/neon/src/instance/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::any::Any;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};

use once_cell::sync::OnceCell;

use crate::context::Context;
use crate::lifecycle::InstanceData;

static COUNTER: AtomicUsize = AtomicUsize::new(0);

fn next_id() -> usize {
COUNTER.fetch_add(1, Ordering::SeqCst)
}

/// A cell that can be used to allocate data that is global to an instance
/// of a Neon addon.
pub struct Global<T> {
_type: PhantomData<T>,
id: OnceCell<usize>,
}

impl<T> Global<T> {
pub const fn new() -> Self {
Self {
_type: PhantomData,
id: OnceCell::new(),
}
}

fn id(&self) -> usize {
*self.id.get_or_init(next_id)
}
}

impl<T: Any + Send> Global<T> {
pub fn borrow<'a, 'b, C>(&self, cx: &'b mut C) -> Option<&'b T>
where
C: Context<'a>,
{
InstanceData::globals(cx)[self.id()]
.as_ref()
.map(|boxed| boxed.downcast_ref().unwrap())
}

pub fn borrow_mut<'a, 'b, C>(&self, cx: &'b mut C) -> Option<&'b mut T>
where
C: Context<'a>,
{
InstanceData::globals(cx)[self.id()]
.as_mut()
.map(|boxed| boxed.downcast_mut().unwrap())
}

pub fn set<'a, 'b, C>(&self, cx: &'b mut C, v: T)
where
C: Context<'a>,
{
InstanceData::globals(cx)[self.id()] = Some(Box::new(v));
}
}

impl<T: Any + Send + Clone> Global<T> {
pub fn get<'a, C>(&self, cx: &mut C) -> Option<T>
where
C: Context<'a>,
{
InstanceData::globals(cx)[self.id()]
.as_ref()
.map(|boxed| boxed.downcast_ref::<T>().unwrap().clone())
}
}
2 changes: 2 additions & 0 deletions crates/neon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
pub mod context;
pub mod event;
pub mod handle;
#[cfg(feature = "napi-6")]
pub mod instance;
pub mod meta;
pub mod object;
pub mod prelude;
Expand Down
48 changes: 44 additions & 4 deletions crates/neon/src/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
//!
//! [napi-docs]: https://nodejs.org/api/n-api.html#n_api_environment_life_cycle_apis

use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
use std::{
any::Any,
ops::{Index, IndexMut},
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};

use crate::{
Expand Down Expand Up @@ -54,6 +58,36 @@ pub(crate) struct InstanceData {

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

/// Table of user-defined global cells.
globals: GlobalTable,
}

pub(crate) struct GlobalTable {
cells: Vec<Option<Box<dyn Any + Send>>>,
}

impl GlobalTable {
fn new() -> Self {
Self { cells: Vec::new() }
}
}

impl Index<usize> for GlobalTable {
type Output = Option<Box<dyn Any + Send>>;

fn index(&self, index: usize) -> &Self::Output {
&self.cells[index]
}
}

impl IndexMut<usize> for GlobalTable {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
if index >= self.cells.len() {
self.cells.resize_with(index + 1, Default::default);
}
&mut self.cells[index]
}
}

/// Wrapper for raw Node-API values to be dropped on the main thread
Expand Down Expand Up @@ -83,7 +117,7 @@ impl InstanceData {
/// # Safety
/// No additional locking (e.g., `Mutex`) is necessary because holding a
/// `Context` reference ensures serialized access.
pub(crate) fn get<'a, C: Context<'a>>(cx: &mut C) -> &'a mut InstanceData {
pub(crate) fn get<'a, C: Context<'a>>(cx: &mut C) -> &mut InstanceData {
let env = cx.env().to_raw();
let data = unsafe { lifecycle::get_instance_data::<InstanceData>(env).as_mut() };

Expand All @@ -107,6 +141,7 @@ impl InstanceData {
id: InstanceId::next(),
drop_queue: Arc::new(drop_queue),
shared_channel,
globals: GlobalTable::new(),
};

unsafe { &mut *lifecycle::set_instance_data(env, data) }
Expand All @@ -129,4 +164,9 @@ impl InstanceData {
pub(crate) fn id<'a, C: Context<'a>>(cx: &mut C) -> InstanceId {
InstanceData::get(cx).id
}

/// Helper to return a reference to the `globals` field of `InstanceData`.
pub(crate) fn globals<'a, C: Context<'a>>(cx: &mut C) -> &mut GlobalTable {
&mut InstanceData::get(cx).globals
}
}
37 changes: 36 additions & 1 deletion test/napi/lib/workers.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const assert = require("assert");
const { Worker, isMainThread, parentPort } = require("worker_threads");
const { Worker, isMainThread, parentPort, threadId } = require("worker_threads");

const addon = require("..");

// Receive a message, try that method and return the error message
if (!isMainThread) {
addon.set_thread_id(threadId);
parentPort.once("message", (message) => {
try {
switch (message) {
Expand All @@ -17,6 +18,12 @@ if (!isMainThread) {
case "get_or_init_clone":
addon.get_or_init_clone(() => ({}));
break;
case "get_thread_id":
{
let id = addon.get_thread_id();
parentPort.postMessage(id);
}
break;
default:
throw new Error(`Unexpected message: ${message}`);
}
Expand All @@ -30,6 +37,11 @@ if (!isMainThread) {
return;
}

// From here on, we're in the main thread.

// Set the `THREAD_ID` Global value in the main thread cell.
addon.set_thread_id(threadId);

describe("Worker / Root Tagging Tests", () => {
describe("Single Threaded", () => {
it("should be able to stash a global with `get_and_replace`", () => {
Expand Down Expand Up @@ -106,3 +118,26 @@ describe("Worker / Root Tagging Tests", () => {
});
});
});

describe("Globals", () => {
it("should be able to read an instance global from the main thread", () => {
let lookedUpId = addon.get_thread_id();
assert.strictEqual(lookedUpId, threadId);
});

it("should allocate separate globals for each addon instance", (cb) => {
let mainThreadId = addon.get_thread_id();

const worker = new Worker(__filename);

worker.once("message", (message) => {
assert.strictEqual(typeof message, 'number');
assert.notStrictEqual(message, mainThreadId);
let mainThreadIdAgain = addon.get_thread_id();
assert.strictEqual(mainThreadIdAgain, mainThreadId);
cb();
});

worker.postMessage("get_thread_id");
});
});
17 changes: 17 additions & 0 deletions test/napi/src/js/workers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::sync::Mutex;

use once_cell::sync::{Lazy, OnceCell};

use neon::instance::Global;
use neon::prelude::*;

pub fn get_and_replace(mut cx: FunctionContext) -> JsResult<JsValue> {
Expand Down Expand Up @@ -44,3 +45,19 @@ pub fn get_or_init_clone(mut cx: FunctionContext) -> JsResult<JsObject> {
// test the `clone` method.
Ok(o.clone(&mut cx).into_inner(&mut cx))
}

static THREAD_ID: Global<u32> = Global::new();

pub fn set_thread_id(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let id = cx.argument::<JsNumber>(0)?.value(&mut cx) as u32;
THREAD_ID.set(&mut cx, id);
Ok(cx.undefined())
}

pub fn get_thread_id(mut cx: FunctionContext) -> JsResult<JsValue> {
let id = THREAD_ID.get(&mut cx);
Ok(match id {
Some(id) => cx.number(id).upcast(),
None => cx.undefined().upcast(),
})
}
2 changes: 2 additions & 0 deletions test/napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("get_and_replace", js::workers::get_and_replace)?;
cx.export_function("get_or_init", js::workers::get_or_init)?;
cx.export_function("get_or_init_clone", js::workers::get_or_init_clone)?;
cx.export_function("get_thread_id", js::workers::get_thread_id)?;
cx.export_function("set_thread_id", js::workers::set_thread_id)?;

Ok(())
}

0 comments on commit 94111d4

Please sign in to comment.