use std::{
cell::Cell,
ffi::{c_void, CStr},
future::Future,
marker::PhantomData,
mem,
pin::Pin,
ptr,
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
task::{Context, Poll, Waker},
thread::{self, ThreadId},
};
use tokio::sync::Mutex;
use futures::{channel::oneshot, task::AtomicWaker};
use futures_core::Stream;
use tokio_stream::StreamExt;
#[cfg(all(feature = "napi4", not(feature = "noop")))]
use crate::bindgen_prelude::{current_custom_gc_handle, CustomGcHandle};
use crate::{
bindgen_prelude::{
BufferSlice, CallbackContext, Either, FromNapiValue, Function, FunctionRef, JsObjectValue,
Object, PromiseRaw, ToNapiValue, TypeName, Undefined, Unknown, ValidateNapiValue,
NAPI_AUTO_LENGTH,
},
check_status, sys,
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
Env, Error, JsError, JsValue, Result, Status, Value, ValueType,
};
pub struct ReadableStream<'env, T> {
pub(crate) value: sys::napi_value,
pub(crate) env: sys::napi_env,
_marker: PhantomData<&'env T>,
}
impl<'env, T> JsValue<'env> for ReadableStream<'env, T> {
fn value(&self) -> Value {
Value {
env: self.env,
value: self.value,
value_type: ValueType::Object,
}
}
}
impl<'env, T> JsObjectValue<'env> for ReadableStream<'env, T> {}
impl<T> TypeName for ReadableStream<'_, T> {
fn type_name() -> &'static str {
"ReadableStream"
}
fn value_type() -> ValueType {
ValueType::Object
}
}
impl<T> ValidateNapiValue for ReadableStream<'_, T> {
unsafe fn validate(
env: napi_sys::napi_env,
napi_val: napi_sys::napi_value,
) -> Result<napi_sys::napi_value> {
let constructor = Env::from(env)
.get_global()?
.get_named_property_unchecked::<Function>("ReadableStream")?;
let mut is_instance = false;
check_status!(
unsafe { sys::napi_instanceof(env, napi_val, constructor.value, &mut is_instance) },
"Check ReadableStream instance failed"
)?;
if !is_instance {
return Err(Error::new(
Status::InvalidArg,
"Value is not a ReadableStream",
));
}
Ok(ptr::null_mut())
}
}
impl<T> FromNapiValue for ReadableStream<'_, T> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
Ok(Self {
value: napi_val,
env,
_marker: PhantomData,
})
}
}
impl<T> ReadableStream<'_, T> {
pub fn locked(&self) -> Result<bool> {
let mut locked = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(self.env, self.value, c"locked".as_ptr().cast(), &mut locked)
},
"Get locked property failed"
)?;
unsafe { FromNapiValue::from_napi_value(self.env, locked) }
}
pub fn cancel(&mut self, reason: Option<String>) -> Result<PromiseRaw<'_, ()>> {
let mut cancel_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
self.value,
c"cancel".as_ptr().cast(),
&mut cancel_fn,
)
},
"Get cancel property failed"
)?;
let reason_value = unsafe { ToNapiValue::to_napi_value(self.env, cancel_reason(reason))? };
let mut promise = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
self.value,
cancel_fn,
1,
[reason_value].as_ptr(),
&mut promise,
)
},
"Call cancel function failed"
)?;
Ok(PromiseRaw::new(self.env, promise))
}
}
impl<T: FromNapiValue> ReadableStream<'_, T> {
pub fn read(&self) -> Result<Reader<T>> {
let mut reader_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
self.value,
c"getReader".as_ptr().cast(),
&mut reader_function,
)
},
"Get getReader on ReadableStream failed"
)?;
let mut reader = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
self.value,
reader_function,
0,
ptr::null_mut(),
&mut reader,
)
},
"Call getReader on ReadableStreamReader failed"
)?;
let mut read_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
reader,
c"read".as_ptr().cast(),
&mut read_function,
)
},
"Get read from ReadableStreamDefaultReader failed"
)?;
let mut bind_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
read_function,
c"bind".as_ptr().cast(),
&mut bind_function,
)
},
"Get bind from ReadableStreamDefaultReader::read failed"
)?;
let mut bind_read = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
read_function,
bind_function,
1,
[reader].as_ptr(),
&mut bind_read,
)
},
"Call bind from ReadableStreamDefaultReader::read failed"
)?;
let read_function = unsafe {
Function::<(), PromiseRaw<IteratorValue<T>>>::from_napi_value(self.env, bind_read)?
}
.build_threadsafe_function()
.callee_handled::<true>()
.weak::<true>()
.build()?;
let signal = Arc::new(CancelSignal {
cancelled: AtomicBool::new(false),
waker: AtomicWaker::new(),
});
Ok(Reader {
inner: read_function,
state: Arc::new(ReaderState {
inner: std::sync::Mutex::new(ReaderInner {
chunk: Ok(None),
done: false,
reading: false,
waker: None,
}),
signal: signal.clone(),
}),
cancel: ReaderCancelHandle {
cancel_fn: bind_reader_cancel(self.env, reader).map(Arc::new),
signal,
env: ReaderEnv::current(self.env),
},
})
}
}
type ReaderCancelFn = ThreadsafeFunction<
Option<String>,
Unknown<'static>,
Either<String, Undefined>,
Status,
false,
true,
>;
struct BoundCancel {
threadsafe: ReaderCancelFn,
js: FunctionRef<Either<String, Undefined>, Unknown<'static>>,
queued: Arc<QueuedCancels>,
}
#[derive(Default)]
struct QueuedCancels {
state: std::sync::Mutex<QueuedCancelsState>,
}
#[derive(Default)]
struct QueuedCancelsState {
closed: bool,
next_key: u64,
senders: Vec<(u64, oneshot::Sender<Result<()>>)>,
}
impl QueuedCancels {
fn lock(&self) -> std::sync::MutexGuard<'_, QueuedCancelsState> {
self
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn insert(&self, sender: oneshot::Sender<Result<()>>) -> Option<u64> {
let mut state = self.lock();
if state.closed {
return None;
}
let key = state.next_key;
state.next_key = key.wrapping_add(1);
state.senders.push((key, sender));
Some(key)
}
fn take(&self, key: u64) -> Option<oneshot::Sender<Result<()>>> {
let mut state = self.lock();
let index = state.senders.iter().position(|(k, _)| *k == key)?;
Some(state.senders.swap_remove(index).1)
}
fn close(&self) {
let senders = {
let mut state = self.lock();
state.closed = true;
mem::take(&mut state.senders)
};
drop(senders);
}
}
struct CloseOnDrop(Arc<QueuedCancels>);
impl Drop for CloseOnDrop {
fn drop(&mut self) {
self.0.close();
}
}
fn cancel_reason(reason: Option<String>) -> Either<String, Undefined> {
match reason {
Some(reason) => Either::A(reason),
None => Either::B(()),
}
}
fn bind_reader_cancel(env: sys::napi_env, reader: sys::napi_value) -> Option<BoundCancel> {
let bind = || -> Result<BoundCancel> {
let mut cancel_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(env, reader, c"cancel".as_ptr().cast(), &mut cancel_function)
},
"Get cancel from ReadableStreamDefaultReader failed"
)?;
let mut value_type = 0;
check_status!(
unsafe { sys::napi_typeof(env, cancel_function, &mut value_type) },
"Get type of ReadableStreamDefaultReader::cancel failed"
)?;
if value_type != sys::ValueType::napi_function {
return Err(Error::new(
Status::FunctionExpected,
"ReadableStreamDefaultReader::cancel is not a function",
));
}
let reader = unsafe { Unknown::from_raw_unchecked(env, reader) };
let unbound = unsafe {
Function::<Either<String, Undefined>, Unknown<'static>>::from_napi_value(
env,
cancel_function,
)?
};
let cancel_function = unbound.bind(reader)?;
let js = cancel_function.create_ref()?;
let queued = Arc::new(QueuedCancels::default());
let close_on_drop = CloseOnDrop(queued.clone());
let threadsafe = cancel_function
.build_threadsafe_function::<Option<String>>()
.callee_handled::<false>()
.weak::<true>()
.build_callback(move |ctx| {
let _ = &close_on_drop;
Ok(cancel_reason(ctx.value))
})?;
Ok(BoundCancel {
threadsafe,
js,
queued,
})
};
match bind() {
Ok(bound) => Some(bound),
Err(_) => {
let mut pending_exception = ptr::null_mut();
unsafe {
sys::napi_get_and_clear_last_exception(env, &mut pending_exception);
}
None
}
}
}
impl<T: ToNapiValue + Send + 'static> ReadableStream<'_, T> {
pub fn new<S: Stream<Item = Result<T>> + Unpin + Send + 'static>(
env: &Env,
inner: S,
) -> Result<Self> {
let global = env.get_global()?;
let constructor = global.get_named_property_unchecked::<Unknown>("ReadableStream")?;
if constructor.get_type()? == ValueType::Undefined {
return Err(Error::new(
Status::GenericFailure,
"ReadableStream is not supported in this Node.js version",
));
}
let state = StreamState::new(inner);
let state_ptr = Arc::into_raw(state) as *mut c_void;
let mut underlying_source = Object::new(env)?;
let mut pull_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"pull".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(pull_callback::<T, S>),
state_ptr,
&mut pull_fn,
)
},
"Failed to create pull function"
)?;
underlying_source.set_named_property("pull", pull_fn)?;
let mut cancel_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"cancel".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(cancel_callback::<S>),
state_ptr,
&mut cancel_fn,
)
},
"Failed to create cancel function"
)?;
underlying_source.set_named_property("cancel", cancel_fn)?;
register_invoke::<S>(env.raw(), underlying_source.0.value, state_ptr)?;
let mut stream = ptr::null_mut();
check_status!(
unsafe {
sys::napi_new_instance(
env.0,
constructor.0.value,
1,
[underlying_source.0.value].as_ptr(),
&mut stream,
)
},
"Create ReadableStream instance failed"
)?;
Ok(Self {
value: stream,
env: env.0,
_marker: PhantomData,
})
}
pub fn with_readable_stream_class<S: Stream<Item = Result<T>> + Unpin + Send + 'static>(
env: &Env,
readable_stream_class: &Unknown,
inner: S,
) -> Result<Self> {
if readable_stream_class.get_type()? == ValueType::Undefined {
return Err(Error::new(
Status::GenericFailure,
"ReadableStream is not supported in this Node.js version",
));
}
let state = StreamState::new(inner);
let state_ptr = Arc::into_raw(state) as *mut c_void;
let mut underlying_source = Object::new(env)?;
let mut pull_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"pull".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(pull_callback::<T, S>),
state_ptr,
&mut pull_fn,
)
},
"Failed to create pull function"
)?;
underlying_source.set_named_property("pull", pull_fn)?;
let mut cancel_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"cancel".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(cancel_callback::<S>),
state_ptr,
&mut cancel_fn,
)
},
"Failed to create cancel function"
)?;
underlying_source.set_named_property("cancel", cancel_fn)?;
register_invoke::<S>(env.raw(), underlying_source.0.value, state_ptr)?;
let mut stream = ptr::null_mut();
check_status!(
unsafe {
sys::napi_new_instance(
env.0,
readable_stream_class.0.value,
1,
[underlying_source.0.value].as_ptr(),
&mut stream,
)
},
"Create ReadableStream instance failed"
)?;
Ok(Self {
value: stream,
env: env.0,
_marker: PhantomData,
})
}
}
impl<'env> ReadableStream<'env, BufferSlice<'env>> {
pub fn create_with_stream_bytes<
B: Into<Vec<u8>>,
S: Stream<Item = Result<B>> + Unpin + Send + 'static,
>(
env: &Env,
inner: S,
) -> Result<Self> {
let global = env.get_global()?;
let constructor = global.get_named_property_unchecked::<Function>("ReadableStream")?;
let state = StreamState::new(inner);
let state_ptr = Arc::into_raw(state) as *mut c_void;
let mut underlying_source = Object::new(env)?;
let mut pull_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"pull".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(pull_callback_bytes::<B, S>),
state_ptr,
&mut pull_fn,
)
},
"Failed to create pull function"
)?;
underlying_source.set_named_property("pull", pull_fn)?;
let mut cancel_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"cancel".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(cancel_callback::<S>),
state_ptr,
&mut cancel_fn,
)
},
"Failed to create cancel function"
)?;
underlying_source.set_named_property("cancel", cancel_fn)?;
register_invoke::<S>(env.raw(), underlying_source.0.value, state_ptr)?;
underlying_source.set("type", "bytes")?;
let mut stream = ptr::null_mut();
check_status!(
unsafe {
sys::napi_new_instance(
env.0,
constructor.value,
1,
[underlying_source.0.value].as_ptr(),
&mut stream,
)
},
"Create ReadableStream instance failed"
)?;
Ok(Self {
value: stream,
env: env.0,
_marker: PhantomData,
})
}
pub fn with_stream_bytes_and_readable_stream_class<
B: Into<Vec<u8>>,
S: Stream<Item = Result<B>> + Unpin + Send + 'static,
>(
env: &Env,
readable_stream_class: &Unknown,
inner: S,
) -> Result<Self> {
if readable_stream_class.get_type()? == ValueType::Undefined {
return Err(Error::new(
Status::GenericFailure,
"ReadableStream is not supported in this Node.js version",
));
}
let state = StreamState::new(inner);
let state_ptr = Arc::into_raw(state) as *mut c_void;
let mut underlying_source = Object::new(env)?;
let mut pull_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"pull".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(pull_callback_bytes::<B, S>),
state_ptr,
&mut pull_fn,
)
},
"Failed to create pull function"
)?;
underlying_source.set_named_property("pull", pull_fn)?;
let mut cancel_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"cancel".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(cancel_callback::<S>),
state_ptr,
&mut cancel_fn,
)
},
"Failed to create cancel function"
)?;
underlying_source.set_named_property("cancel", cancel_fn)?;
register_invoke::<S>(env.raw(), underlying_source.0.value, state_ptr)?;
underlying_source.set("type", "bytes")?;
let mut stream = ptr::null_mut();
check_status!(
unsafe {
sys::napi_new_instance(
env.0,
readable_stream_class.0.value,
1,
[underlying_source.0.value].as_ptr(),
&mut stream,
)
},
"Create ReadableStream instance failed"
)?;
Ok(Self {
value: stream,
env: env.0,
_marker: PhantomData,
})
}
}
pub struct IteratorValue<'env, T: FromNapiValue> {
_marker: PhantomData<&'env ()>,
value: Option<T>,
done: bool,
}
impl<T: FromNapiValue> FromNapiValue for IteratorValue<'_, T> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut done = ptr::null_mut();
check_status!(
unsafe { sys::napi_get_named_property(env, napi_val, c"done".as_ptr().cast(), &mut done) },
"Get done property failed"
)?;
let done = unsafe { FromNapiValue::from_napi_value(env, done)? };
let mut value = ptr::null_mut();
check_status!(
unsafe { sys::napi_get_named_property(env, napi_val, c"value".as_ptr().cast(), &mut value) },
"Get value property failed"
)?;
let value = unsafe { FromNapiValue::from_napi_value(env, value)? };
Ok(Self {
value,
done,
_marker: PhantomData,
})
}
}
struct ReaderInner<T: FromNapiValue + 'static> {
chunk: Result<Option<T>>,
done: bool,
reading: bool,
waker: Option<Waker>,
}
struct ReaderState<T: FromNapiValue + 'static> {
inner: std::sync::Mutex<ReaderInner<T>>,
signal: Arc<CancelSignal>,
}
struct CancelSignal {
cancelled: AtomicBool,
waker: AtomicWaker,
}
impl CancelSignal {
fn cancel(&self) {
self.cancelled.store(true, Ordering::SeqCst);
self.waker.wake();
}
fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
fn release_waker(&self) {
drop(self.waker.take());
}
}
impl std::panic::RefUnwindSafe for CancelSignal {}
#[derive(Clone)]
struct ReaderEnv {
address: usize,
thread: ThreadId,
#[cfg(all(feature = "napi4", not(feature = "noop")))]
custom_gc: Option<Arc<CustomGcHandle>>,
}
impl ReaderEnv {
fn current(env: sys::napi_env) -> Self {
Self {
address: env as usize,
thread: thread::current().id(),
#[cfg(all(feature = "napi4", not(feature = "noop")))]
custom_gc: current_custom_gc_handle(),
}
}
fn is_current(&self, env: &Env) -> bool {
if self.address != env.raw() as usize || self.thread != thread::current().id() {
return false;
}
#[cfg(all(feature = "napi4", not(feature = "noop")))]
if let Some(custom_gc) = &self.custom_gc {
return !custom_gc.with_read_aborted(|aborted| aborted);
}
true
}
}
pub struct Reader<T: FromNapiValue + 'static> {
inner:
ThreadsafeFunction<(), PromiseRaw<'static, IteratorValue<'static, T>>, (), Status, true, true>,
state: Arc<ReaderState<T>>,
cancel: ReaderCancelHandle,
}
const _: fn() = || {
fn unwind_safe<T: std::panic::RefUnwindSafe + std::panic::UnwindSafe>() {}
unwind_safe::<Reader<u32>>();
};
impl<T: FromNapiValue + 'static> Reader<T> {
pub fn cancel_handle(&self) -> ReaderCancelHandle {
self.cancel.clone()
}
pub fn cancel(
&self,
reason: Option<String>,
) -> impl Future<Output = Result<()>> + Send + 'static {
self.cancel.cancel(reason)
}
pub fn cancel_on_js_thread(
&self,
env: &Env,
reason: Option<String>,
) -> Result<impl Future<Output = Result<()>> + Send + 'static> {
self.cancel.cancel_on_js_thread(env, reason)
}
}
#[derive(Clone)]
pub struct ReaderCancelHandle {
cancel_fn: Option<Arc<BoundCancel>>,
signal: Arc<CancelSignal>,
env: ReaderEnv,
}
impl ReaderCancelHandle {
pub fn cancel(
&self,
reason: Option<String>,
) -> impl Future<Output = Result<()>> + Send + 'static {
self.signal.cancel();
let receiver = match &self.cancel_fn {
None => Err(no_cancel_method()),
Some(bound) => {
let (sender, receiver) = oneshot::channel::<Result<()>>();
match bound.queued.insert(sender) {
None => Err(Error::new(
Status::Closing,
"Failed to schedule ReadableStream cancel",
)),
Some(key) => {
let queued = bound.queued.clone();
let status = bound.threadsafe.call_with_return_value(
reason,
ThreadsafeFunctionCallMode::NonBlocking,
move |returned, env| {
if let Some(sender) = queued.take(key) {
settle_cancel(returned, env, sender);
}
Ok(())
},
);
if status == Status::Ok {
Ok(receiver)
} else {
drop(bound.queued.take(key));
Err(Error::new(
status,
"Failed to schedule ReadableStream cancel",
))
}
}
}
}
};
cancel_outcome(receiver)
}
pub fn cancel_on_js_thread(
&self,
env: &Env,
reason: Option<String>,
) -> Result<impl Future<Output = Result<()>> + Send + 'static> {
if !self.env.is_current(env) {
return Err(Error::new(
Status::InvalidArg,
"ReaderCancelHandle::cancel_on_js_thread must be called on the stream's JavaScript thread, with its env",
));
}
self.signal.cancel();
let Some(bound) = &self.cancel_fn else {
return Err(no_cancel_method());
};
let raw_env = env.raw();
let cancel_function = bound.js.borrow_back(env)?;
let reason = unsafe { ToNapiValue::to_napi_value(raw_env, cancel_reason(reason))? };
let mut this = ptr::null_mut();
check_status!(
unsafe { sys::napi_get_undefined(raw_env, &mut this) },
"Get undefined failed"
)?;
let mut returned = ptr::null_mut();
let status = unsafe {
sys::napi_call_function(
raw_env,
this,
cancel_function.value,
1,
[reason].as_ptr(),
&mut returned,
)
};
if status == sys::Status::napi_pending_exception {
let reason = take_exception_message(raw_env)
.unwrap_or_else(|| "ReadableStream reader cancel threw".to_owned());
return Err(Error::new(Status::GenericFailure, reason));
}
check_status!(status, "Call ReadableStream reader cancel failed")?;
let (sender, receiver) = oneshot::channel::<Result<()>>();
settle_cancel(
Ok(unsafe { Unknown::from_raw_unchecked(raw_env, returned) }),
*env,
sender,
);
Ok(cancel_outcome(Ok(receiver)))
}
}
fn no_cancel_method() -> Error {
Error::new(
Status::FunctionExpected,
"ReadableStream reader has no cancel method",
)
}
async fn cancel_outcome(receiver: Result<oneshot::Receiver<Result<()>>>) -> Result<()> {
receiver?.await.unwrap_or_else(|_| {
Err(Error::new(
Status::GenericFailure,
"ReadableStream cancel was dropped before it settled",
))
})
}
fn settle_cancel(returned: Result<Unknown<'_>>, env: Env, sender: oneshot::Sender<Result<()>>) {
let raw_env = env.raw();
let slot: CancelSlot = Rc::new(Cell::new(Some(sender)));
let setup = (|| -> Result<()> {
let returned = returned?;
let follow = compile_follow_like_await(raw_env)?;
let fulfilled = settle_function(raw_env, c"fulfilled", settle_fulfilled, slot.clone())?;
let rejected = settle_function(raw_env, c"rejected", settle_rejected, slot.clone())?;
let mut this = ptr::null_mut();
check_status!(
unsafe { sys::napi_get_undefined(raw_env, &mut this) },
"Get undefined failed"
)?;
let mut settled = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
raw_env,
this,
follow,
3,
[returned.raw(), fulfilled, rejected].as_ptr(),
&mut settled,
)
},
"Follow what ReadableStream reader cancel returned failed"
)?;
Ok(())
})();
if let Err(err) = setup {
let err = match take_exception_message(raw_env) {
Some(reason) => {
drop(err);
Error::new(Status::GenericFailure, reason)
}
None => into_owned_error(raw_env, err, "ReadableStream cancel failed"),
};
send_outcome(&slot, Err(err));
}
}
const FOLLOW_LIKE_AWAIT: &CStr = c"(async (value, fulfilled, rejected) => {\
try { await value } catch (error) { rejected(error); return }\
fulfilled();\
})";
type CancelSlot = Rc<Cell<Option<oneshot::Sender<Result<()>>>>>;
fn send_outcome(slot: &CancelSlot, result: Result<()>) {
if let Some(sender) = slot.take() {
let _ = sender.send(result);
}
}
fn compile_follow_like_await(env: sys::napi_env) -> Result<sys::napi_value> {
let mut source = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_string_utf8(
env,
FOLLOW_LIKE_AWAIT.as_ptr(),
NAPI_AUTO_LENGTH,
&mut source,
)
},
"Create the source of the ReadableStream cancel follower failed"
)?;
let mut follow = ptr::null_mut();
check_status!(
unsafe { sys::napi_run_script(env, source, &mut follow) },
"Compile the ReadableStream cancel follower failed"
)?;
Ok(follow)
}
fn settle_function(
env: sys::napi_env,
name: &CStr,
callback: unsafe extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value,
slot: CancelSlot,
) -> Result<sys::napi_value> {
let data = Box::into_raw(Box::new(slot));
let mut function = ptr::null_mut();
let mut status = unsafe {
sys::napi_create_function(
env,
name.as_ptr(),
NAPI_AUTO_LENGTH,
Some(callback),
data.cast(),
&mut function,
)
};
if status == sys::Status::napi_ok {
status = unsafe {
sys::napi_wrap(
env,
function,
data.cast(),
Some(drop_cancel_slot),
ptr::null_mut(),
ptr::null_mut(),
)
};
}
if status != sys::Status::napi_ok {
drop(unsafe { Box::from_raw(data) });
}
check_status!(status, "Create a ReadableStream cancel callback failed")?;
Ok(function)
}
unsafe extern "C" fn settle_fulfilled(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut data = ptr::null_mut();
let status = unsafe {
sys::napi_get_cb_info(
env,
info,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
&mut data,
)
};
if status == sys::Status::napi_ok && !data.is_null() {
send_outcome(unsafe { &*data.cast::<CancelSlot>() }, Ok(()));
}
ptr::null_mut()
}
unsafe extern "C" fn settle_rejected(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut argc = 1;
let mut argv = [ptr::null_mut(); 1];
let mut data = ptr::null_mut();
let status = unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
ptr::null_mut(),
&mut data,
)
};
if status == sys::Status::napi_ok && !data.is_null() {
let reason = rejection_message(unsafe { Unknown::from_raw_unchecked(env, argv[0]) });
send_outcome(
unsafe { &*data.cast::<CancelSlot>() },
Err(Error::new(Status::GenericFailure, reason)),
);
}
ptr::null_mut()
}
extern "C" fn drop_cancel_slot(
_env: sys::napi_env,
finalize_data: *mut c_void,
_finalize_hint: *mut c_void,
) {
drop(unsafe { Box::from_raw(finalize_data.cast::<CancelSlot>()) });
}
fn take_exception_message(env: sys::napi_env) -> Option<String> {
let mut pending = false;
if unsafe { sys::napi_is_exception_pending(env, &mut pending) } != sys::Status::napi_ok
|| !pending
{
return None;
}
let mut exception = ptr::null_mut();
if unsafe { sys::napi_get_and_clear_last_exception(env, &mut exception) } != sys::Status::napi_ok
|| exception.is_null()
{
return None;
}
Some(rejection_message(unsafe {
Unknown::from_raw_unchecked(env, exception)
}))
}
fn into_owned_error(env: sys::napi_env, err: Error, context: &str) -> Error {
let mut pending_exception = ptr::null_mut();
unsafe {
sys::napi_get_and_clear_last_exception(env, &mut pending_exception);
}
let reason = if err.reason.is_empty() {
format!("{context}: {}", err.status)
} else {
err.reason.clone()
};
drop(err);
Error::new(Status::GenericFailure, reason)
}
fn rejection_message(value: Unknown) -> String {
fn from_message_property(value: Unknown) -> Result<String> {
let object = value.coerce_to_object()?;
let message = object.get_named_property::<Unknown>("message")?;
message.coerce_to_string()?.into_utf8()?.into_owned()
}
let message = if matches!(value.get_type(), Ok(ValueType::Object)) {
from_message_property(value).ok()
} else {
None
}
.unwrap_or_else(|| {
value
.coerce_to_string()
.and_then(|s| s.into_utf8())
.and_then(|s| s.into_owned())
.unwrap_or_else(|_| "ReadableStream read error".to_owned())
});
let mut pending_exception = ptr::null_mut();
unsafe {
sys::napi_get_and_clear_last_exception(value.0.env, &mut pending_exception);
}
message
}
impl<T: FromNapiValue + 'static> Drop for Reader<T> {
fn drop(&mut self) {
self.state.signal.release_waker();
let waker = self
.state
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.waker
.take();
drop(waker);
}
}
impl<T: FromNapiValue + 'static> futures_core::Stream for Reader<T> {
type Item = Result<T>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.state.signal.waker.register(cx.waker());
let issue_read = {
let mut inner = self
.state
.inner
.lock()
.map_err(|_| Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next"))?;
if self.state.signal.is_cancelled() {
inner.chunk = Ok(None);
inner.done = true;
inner.waker = None;
self.state.signal.release_waker();
return Poll::Ready(None);
}
match mem::replace(&mut inner.chunk, Ok(None)) {
Ok(Some(chunk)) => return Poll::Ready(Some(Ok(chunk))),
Err(err) => {
self.state.signal.release_waker();
return Poll::Ready(Some(Err(err)));
}
Ok(None) => {}
}
if inner.done {
self.state.signal.release_waker();
return Poll::Ready(None);
}
inner.waker = Some(cx.waker().clone());
if inner.reading {
false
} else {
inner.reading = true;
true
}
};
if issue_read {
let state = self.state.clone();
let state_in_catch = self.state.clone();
let state_in_finally = self.state.clone();
let state_on_setup_err = self.state.clone();
let status = self.inner.call_with_return_value(
Ok(()),
ThreadsafeFunctionCallMode::NonBlocking,
move |iterator, env| {
let setup = (move || -> Result<()> {
let iterator = iterator?;
iterator
.then(move |cx| {
let mut inner = state.inner.lock().map_err(|_| {
Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next")
})?;
if inner.done || state.signal.is_cancelled() {
return Ok(());
}
if cx.value.done {
inner.done = true;
}
if let Some(val) = cx.value.value {
inner.chunk = Ok(Some(val));
}
Ok(())
})?
.catch(move |cx: CallbackContext<Unknown>| {
let reason = rejection_message(cx.value);
let mut inner = state_in_catch.inner.lock().map_err(|_| {
Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next")
})?;
if inner.done || state_in_catch.signal.is_cancelled() {
return Ok(());
}
inner.chunk = Err(Error::new(Status::GenericFailure, reason));
inner.done = true;
Ok(())
})?
.finally(move |_| {
let waker = {
let mut inner = state_in_finally.inner.lock().map_err(|_| {
Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next")
})?;
inner.reading = false;
inner.waker.take()
};
if let Some(waker) = waker {
waker.wake();
}
Ok(())
})?;
Ok(())
})();
if let Err(err) = setup {
let owned = into_owned_error(env.raw(), err, "ReadableStream read failed");
let waker = {
let mut inner = state_on_setup_err.inner.lock().map_err(|_| {
Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next")
})?;
inner.reading = false;
inner.done = true;
inner.chunk = Err(owned);
inner.waker.take()
};
if let Some(waker) = waker {
waker.wake();
}
}
Ok(())
},
);
if status != Status::Ok {
let mut inner = self
.state
.inner
.lock()
.map_err(|_| Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next"))?;
inner.reading = false;
inner.done = true;
inner.waker = None;
self.state.signal.release_waker();
return Poll::Ready(Some(Err(Error::new(
status,
"Failed to schedule ReadableStream read",
))));
}
}
Poll::Pending
}
}
struct StreamState<S> {
stream: Mutex<Option<Pin<Box<S>>>>,
cancelled: AtomicBool,
}
impl<S> StreamState<S> {
fn new(stream: S) -> Arc<Self> {
Arc::new(Self {
stream: Mutex::new(Some(Box::pin(stream))),
cancelled: AtomicBool::new(false),
})
}
}
extern "C" fn invoke<S>(
_env: sys::napi_env,
finalize_data: *mut c_void,
_finalize_hint: *mut c_void,
) {
if !finalize_data.is_null() {
drop(unsafe { Arc::from_raw(finalize_data.cast::<StreamState<S>>()) });
}
}
fn register_invoke<S>(
env: sys::napi_env,
underlying_source: sys::napi_value,
state_ptr: *mut c_void,
) -> Result<()> {
check_status!(
unsafe {
sys::napi_add_finalizer(
env,
underlying_source,
state_ptr,
Some(invoke::<S>),
ptr::null_mut(),
ptr::null_mut(),
)
},
"Failed to add invoke to underlying source"
)
}
struct PullController<T: ToNapiValue> {
enqueue: crate::bindgen_prelude::FunctionRef<T, ()>,
close: crate::bindgen_prelude::FunctionRef<(), ()>,
}
impl<T: ToNapiValue> PullController<T> {
fn from_callback_info(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> Result<(Self, *mut c_void)> {
let mut data = ptr::null_mut();
let mut argc = 1;
let mut args = [ptr::null_mut(); 1];
check_status!(
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
args.as_mut_ptr(),
ptr::null_mut(),
&mut data,
)
},
"Get ReadableStream.pull callback info failed"
)?;
let controller = unsafe { Object::from_napi_value(env, args[0])? };
let enqueue = controller
.get_named_property_unchecked::<Function<T, ()>>("enqueue")?
.bind(controller)?
.create_ref()?;
let close = controller
.get_named_property_unchecked::<Function<(), ()>>("close")?
.bind(controller)?
.create_ref()?;
Ok((Self { enqueue, close }, data))
}
}
extern "C" fn cancel_callback<S>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut data = ptr::null_mut();
unsafe {
sys::napi_get_cb_info(
env,
info,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
&mut data,
);
}
if !data.is_null() {
let state = unsafe {
Arc::increment_strong_count(data.cast::<StreamState<S>>());
Arc::from_raw(data.cast::<StreamState<S>>())
};
state.cancelled.store(true, Ordering::SeqCst);
if let Ok(mut guard) = state.stream.try_lock() {
let _ = guard.take();
};
}
ptr::null_mut()
}
extern "C" fn pull_callback<
T: ToNapiValue + Send + 'static,
S: Stream<Item = Result<T>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
match pull_callback_impl::<T, S>(env, info) {
Ok(val) => val,
Err(err) => unsafe {
let js_error: JsError = err.into();
js_error.throw_into(env);
ptr::null_mut()
},
}
}
fn pull_callback_impl<
T: ToNapiValue + Send + 'static,
S: Stream<Item = Result<T>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> Result<sys::napi_value> {
let (controller, data) = PullController::<T>::from_callback_info(env, info)?;
let state = unsafe {
Arc::increment_strong_count(data.cast::<StreamState<S>>());
Arc::from_raw(data.cast::<StreamState<S>>())
};
if state.cancelled.load(Ordering::SeqCst) {
return Ok(ptr::null_mut());
}
let env_wrapper = Env::from_raw(env);
let state_for_async = state.clone();
let promise = env_wrapper.spawn_future_with_callback(
async move {
let mut guard = state_for_async.stream.lock().await;
if let Some(ref mut stream) = *guard {
stream.next().await.transpose()
} else {
Ok(None)
}
},
move |env, val| {
let result = {
if state.cancelled.load(Ordering::SeqCst) {
} else if let Some(val) = val {
let enqueue_fn = controller.enqueue.borrow_back(env)?;
enqueue_fn.call(val)?;
} else {
let close_fn = controller.close.borrow_back(env)?;
close_fn.call(())?;
if let Ok(mut guard) = state.stream.try_lock() {
let _ = guard.take();
}
}
Ok::<(), Error>(())
};
drop(controller.enqueue);
drop(controller.close);
result
},
)?;
Ok(promise.inner)
}
extern "C" fn pull_callback_bytes<
B: Into<Vec<u8>>,
S: Stream<Item = Result<B>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
match pull_callback_impl_bytes::<B, S>(env, info) {
Ok(val) => val,
Err(err) => unsafe {
let js_error: JsError = err.into();
js_error.throw_into(env);
ptr::null_mut()
},
}
}
fn pull_callback_impl_bytes<
B: Into<Vec<u8>>,
S: Stream<Item = Result<B>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> Result<sys::napi_value> {
let (controller, data) = PullController::<BufferSlice>::from_callback_info(env, info)?;
let state = unsafe {
Arc::increment_strong_count(data.cast::<StreamState<S>>());
Arc::from_raw(data.cast::<StreamState<S>>())
};
if state.cancelled.load(Ordering::SeqCst) {
return Ok(ptr::null_mut());
}
let env_wrapper = Env::from_raw(env);
let state_for_async = state.clone();
let promise = env_wrapper.spawn_future_with_callback(
async move {
let mut guard = state_for_async.stream.lock().await;
if let Some(ref mut stream) = *guard {
stream
.next()
.await
.transpose()
.map(|v| v.map(|v| Into::<Vec<u8>>::into(v)))
} else {
Ok(None)
}
},
move |env, val| {
let result = {
if state.cancelled.load(Ordering::SeqCst) {
} else if let Some(val) = val {
let enqueue_fn = controller.enqueue.borrow_back(env)?;
enqueue_fn.call(BufferSlice::from_data(env, val)?)?;
} else {
let close_fn = controller.close.borrow_back(env)?;
close_fn.call(())?;
if let Ok(mut guard) = state.stream.try_lock() {
let _ = guard.take();
}
}
Ok::<(), Error>(())
};
drop(controller.enqueue);
drop(controller.close);
result
},
)?;
Ok(promise.inner)
}