use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{mpsc, Arc};
use neon_runtime::raw::Env;
use neon_runtime::tsfn::ThreadsafeFunction;
use crate::context::{Context, TaskContext};
use crate::result::NeonResult;
type Callback = Box<dyn FnOnce(Env) + Send + 'static>;
#[cfg_attr(docsrs, doc(cfg(all(feature = "napi-4", feature = "task-api"))))]
pub struct Channel {
state: Arc<ChannelState>,
has_ref: bool,
}
impl std::fmt::Debug for Channel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Channel")
}
}
impl Channel {
pub fn new<'a, C: Context<'a>>(cx: &mut C) -> Self {
Self {
state: Arc::new(ChannelState::new(cx)),
has_ref: true,
}
}
pub fn unref<'a, C: Context<'a>>(&mut self, cx: &mut C) -> &mut Self {
if !self.has_ref {
return self;
}
self.has_ref = false;
self.state.unref(cx);
self
}
pub fn reference<'a, C: Context<'a>>(&mut self, cx: &mut C) -> &mut Self {
if self.has_ref {
return self;
}
self.has_ref = true;
self.state.reference(cx);
self
}
pub fn send<T, F>(&self, f: F) -> JoinHandle<T>
where
T: Send + 'static,
F: FnOnce(TaskContext) -> NeonResult<T> + Send + 'static,
{
self.try_send(f).unwrap()
}
pub fn try_send<T, F>(&self, f: F) -> Result<JoinHandle<T>, SendError>
where
T: Send + 'static,
F: FnOnce(TaskContext) -> NeonResult<T> + Send + 'static,
{
let (tx, rx) = mpsc::sync_channel(1);
let callback = Box::new(move |env| {
let env = unsafe { std::mem::transmute(env) };
TaskContext::with_context(env, move |cx| {
let _ = tx.send(f(cx).map_err(|_| ()));
});
});
self.state
.tsfn
.call(callback, None)
.map_err(|_| SendError)?;
Ok(JoinHandle { rx })
}
pub fn has_ref(&self) -> bool {
self.has_ref
}
}
impl Clone for Channel {
fn clone(&self) -> Self {
if !self.has_ref {
return Self {
state: self.state.clone(),
has_ref: false,
};
}
let state = Arc::clone(&self.state);
state.ref_count.fetch_add(1, Ordering::Relaxed);
Self {
state,
has_ref: true,
}
}
}
impl Drop for Channel {
fn drop(&mut self) {
if !self.has_ref {
return;
}
if Arc::strong_count(&self.state) == 1 {
return;
}
let state = Arc::clone(&self.state);
self.send(move |mut cx| {
state.unref(&mut cx);
Ok(())
});
}
}
pub struct JoinHandle<T> {
rx: mpsc::Receiver<Result<T, ()>>,
}
impl<T> JoinHandle<T> {
pub fn join(self) -> Result<T, JoinError> {
self.rx
.recv()
.map_err(|_| JoinError(JoinErrorType::Panic))?
.map_err(|_| JoinError(JoinErrorType::Throw))
}
}
#[derive(Debug)]
pub struct JoinError(JoinErrorType);
#[derive(Debug)]
enum JoinErrorType {
Panic,
Throw,
}
impl std::fmt::Display for JoinError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
JoinErrorType::Panic => f.write_str("Closure panicked before returning"),
JoinErrorType::Throw => f.write_str("Closure threw an exception"),
}
}
}
impl std::error::Error for JoinError {}
#[cfg_attr(docsrs, doc(cfg(all(feature = "napi-4", feature = "task-api"))))]
pub struct SendError;
impl std::fmt::Display for SendError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SendError")
}
}
impl std::fmt::Debug for SendError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::error::Error for SendError {}
struct ChannelState {
tsfn: ThreadsafeFunction<Callback>,
ref_count: AtomicUsize,
}
impl ChannelState {
fn new<'a, C: Context<'a>>(cx: &mut C) -> Self {
let tsfn = unsafe { ThreadsafeFunction::new(cx.env().to_raw(), Self::callback) };
Self {
tsfn,
ref_count: AtomicUsize::new(1),
}
}
fn reference<'a, C: Context<'a>>(&self, cx: &mut C) {
if self.ref_count.fetch_add(1, Ordering::Relaxed) != 0 {
return;
}
unsafe {
self.tsfn.reference(cx.env().to_raw());
}
}
fn unref<'a, C: Context<'a>>(&self, cx: &mut C) {
if self.ref_count.fetch_sub(1, Ordering::Relaxed) != 1 {
return;
}
unsafe {
self.tsfn.unref(cx.env().to_raw());
}
}
fn callback(env: Option<Env>, callback: Callback) {
if let Some(env) = env {
callback(env);
} else {
crate::context::internal::IS_RUNNING.with(|v| {
*v.borrow_mut() = false;
});
}
}
}