use std::io;
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicUsize;
use deno_core::AsyncRefCell;
use deno_core::CancelHandle;
use deno_core::ExternalOpsTracker;
use deno_core::RcRef;
use deno_core::serde_json;
use deno_io::BiPipe;
use deno_io::BiPipeRead;
use deno_io::BiPipeWrite;
use memchr::memchr;
use tokio::io::AsyncWriteExt;
#[cfg(unix)]
type ReceivedRawFd = std::os::fd::OwnedFd;
#[cfg(not(unix))]
type ReceivedRawFd = i32;
#[cfg(unix)]
fn into_raw_fd_i32(fd: ReceivedRawFd) -> i32 {
use std::os::fd::IntoRawFd;
fd.into_raw_fd()
}
#[cfg(not(unix))]
fn into_raw_fd_i32(fd: ReceivedRawFd) -> i32 {
fd
}
pub struct IpcRefTracker {
refed: AtomicBool,
tracker: OpsTracker,
}
enum OpsTracker {
External(ExternalOpsTracker),
#[cfg(test)]
Test,
}
impl OpsTracker {
fn ref_(&self) {
match self {
Self::External(tracker) => tracker.ref_op(),
#[cfg(test)]
Self::Test => {}
}
}
fn unref(&self) {
match self {
Self::External(tracker) => tracker.unref_op(),
#[cfg(test)]
Self::Test => {}
}
}
}
impl IpcRefTracker {
pub fn new(tracker: ExternalOpsTracker) -> Self {
Self {
refed: AtomicBool::new(false),
tracker: OpsTracker::External(tracker),
}
}
#[cfg(test)]
fn new_test() -> Self {
Self {
refed: AtomicBool::new(false),
tracker: OpsTracker::Test,
}
}
pub fn ref_(&self) {
if !self.refed.swap(true, std::sync::atomic::Ordering::AcqRel) {
self.tracker.ref_();
}
}
pub fn unref(&self) {
if self.refed.swap(false, std::sync::atomic::Ordering::AcqRel) {
self.tracker.unref();
}
}
}
async fn write_with_optional_fd(
write_half: &mut BiPipeWrite,
msg: &[u8],
#[cfg_attr(
not(unix),
allow(unused_variables, reason = "fd transfer is unix-only")
)]
raw_fd: Option<i32>,
) -> Result<(), io::Error> {
#[cfg(unix)]
if let Some(raw_fd) = raw_fd {
use std::os::fd::AsRawFd;
use std::os::fd::FromRawFd;
let raw_fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(raw_fd) };
let nwritten = write_half.send_with_fd(msg, raw_fd.as_raw_fd()).await?;
if nwritten < msg.len() {
write_half.write_all(&msg[nwritten..]).await?;
}
return Ok(());
}
write_half.write_all(msg).await?;
Ok(())
}
pub struct IpcJsonStreamResource {
pub read_half: AsyncRefCell<IpcJsonStream>,
pub write_half: AsyncRefCell<BiPipeWrite>,
pub cancel: Rc<CancelHandle>,
pub queued_bytes: AtomicUsize,
pub ref_tracker: IpcRefTracker,
}
impl deno_core::Resource for IpcJsonStreamResource {
fn close(self: Rc<Self>) {
self.cancel.cancel();
}
}
impl IpcJsonStreamResource {
pub fn new(
stream: i64,
ref_tracker: IpcRefTracker,
) -> Result<Self, std::io::Error> {
let (read_half, write_half) = BiPipe::from_raw(stream as _)?.split();
Ok(Self {
read_half: AsyncRefCell::new(IpcJsonStream::new(read_half)),
write_half: AsyncRefCell::new(write_half),
cancel: Default::default(),
queued_bytes: Default::default(),
ref_tracker,
})
}
#[cfg(all(unix, test))]
pub fn from_stream(
stream: tokio::net::UnixStream,
ref_tracker: IpcRefTracker,
) -> Self {
let (read_half, write_half) = stream.into_split();
Self {
read_half: AsyncRefCell::new(IpcJsonStream::new(read_half.into())),
write_half: AsyncRefCell::new(write_half.into()),
cancel: Default::default(),
queued_bytes: Default::default(),
ref_tracker,
}
}
#[cfg(all(windows, test))]
pub fn from_stream(
pipe: tokio::net::windows::named_pipe::NamedPipeClient,
ref_tracker: IpcRefTracker,
) -> Self {
let (read_half, write_half) = tokio::io::split(pipe);
Self {
read_half: AsyncRefCell::new(IpcJsonStream::new(read_half.into())),
write_half: AsyncRefCell::new(write_half.into()),
cancel: Default::default(),
queued_bytes: Default::default(),
ref_tracker,
}
}
pub async fn write_msg_bytes(
self: Rc<Self>,
msg: &[u8],
raw_fd: Option<i32>,
) -> Result<(), io::Error> {
let mut write_half = RcRef::map(self, |r| &r.write_half).borrow_mut().await;
write_with_optional_fd(&mut write_half, msg, raw_fd).await
}
}
pub const INITIAL_CAPACITY: usize = 1024 * 64;
struct ReadBuffer {
buffer: Box<[u8]>,
pos: usize,
cap: usize,
#[cfg(unix)]
raw_fd: Option<ReceivedRawFd>,
}
impl ReadBuffer {
fn new() -> Self {
Self {
buffer: vec![0; INITIAL_CAPACITY].into_boxed_slice(),
pos: 0,
cap: 0,
#[cfg(unix)]
raw_fd: None,
}
}
fn get_mut(&mut self) -> &mut [u8] {
&mut self.buffer
}
fn available_mut(&mut self) -> &mut [u8] {
&mut self.buffer[self.pos..self.cap]
}
fn consume(&mut self, n: usize) {
self.pos = std::cmp::min(self.pos + n, self.cap);
}
fn needs_fill(&self) -> bool {
self.pos >= self.cap
}
async fn fill_from_pipe(
&mut self,
pipe: &mut BiPipeRead,
) -> io::Result<usize> {
#[cfg(unix)]
{
use std::os::fd::FromRawFd;
let (nread, raw_fd) = pipe.recv_with_fd(self.get_mut()).await?;
self.cap = nread;
self.pos = 0;
if let Some(raw_fd) = raw_fd {
debug_assert!(self.raw_fd.is_none());
let raw_fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(raw_fd) };
if let Some(previous) = self.raw_fd.replace(raw_fd) {
drop(previous);
}
}
Ok(nread)
}
#[cfg(not(unix))]
{
use tokio::io::AsyncReadExt;
let nread = pipe.read(self.get_mut()).await?;
self.cap = nread;
self.pos = 0;
Ok(nread)
}
}
#[cfg(unix)]
fn take_raw_fd(&mut self) -> Option<ReceivedRawFd> {
self.raw_fd.take()
}
#[cfg(not(unix))]
fn take_raw_fd(&mut self) -> Option<i32> {
None
}
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum IpcJsonStreamError {
#[class(inherit)]
#[error("{0}")]
Io(#[source] std::io::Error),
#[class(generic)]
#[error("{0}")]
Json(#[source] serde_json::Error),
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum IpcAdvancedStreamError {
#[class(inherit)]
#[error("{0}")]
Io(#[source] std::io::Error),
}
pub struct IpcAdvancedStream {
pipe: BiPipeRead,
read_buffer: ReadBuffer,
}
struct MessageLengthBuffer {
buffer: [u8; 4],
pos: u8,
value: u32,
}
impl MessageLengthBuffer {
fn new() -> Self {
Self {
buffer: [0; 4],
pos: 0,
value: 0,
}
}
fn message_len(&mut self) -> Option<usize> {
if self.pos == 4 {
self.value = u32::from_be_bytes(self.buffer);
self.pos = 5;
Some(self.value as usize)
} else if self.pos == 5 {
Some(self.value as usize)
} else {
None
}
}
fn update_pos_by(&mut self, num: usize) {
self.pos = (self.pos + num as u8).min(4);
}
fn available_mut(&mut self) -> &mut [u8] {
&mut self.buffer[self.pos as usize..4]
}
}
impl IpcAdvancedStream {
fn new(pipe: BiPipeRead) -> Self {
Self {
pipe,
read_buffer: ReadBuffer::new(),
}
}
pub async fn read_msg_bytes_and_raw_fd(
&mut self,
) -> Result<Option<(Vec<u8>, Option<i32>)>, IpcAdvancedStreamError> {
let mut length_buffer = MessageLengthBuffer::new();
let mut out_buf = Vec::with_capacity(32);
let mut read = 0usize;
let mut raw_fd = None;
loop {
if self.read_buffer.needs_fill() {
self
.read_buffer
.fill_from_pipe(&mut self.pipe)
.await
.map_err(IpcAdvancedStreamError::Io)?;
}
if let Some(fd) = self.read_buffer.take_raw_fd() {
debug_assert!(raw_fd.is_none());
if raw_fd.is_none() {
raw_fd = Some(fd);
}
}
let available = self.read_buffer.available_mut();
if available.is_empty() {
if read == 0 {
return Ok(None);
} else if length_buffer.message_len().is_some() {
return Err(IpcAdvancedStreamError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"ipc stream closed while reading message",
)));
} else {
return Err(IpcAdvancedStreamError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"ipc stream closed before message length",
)));
}
}
let msg_len = length_buffer.message_len();
let (done, used) = if let Some(msg_len) = msg_len {
if out_buf.len() >= msg_len {
(true, 0)
} else {
let remaining = msg_len - out_buf.len();
out_buf.reserve(remaining);
let to_copy = available.len().min(remaining);
out_buf.extend_from_slice(&available[..to_copy]);
(out_buf.len() == msg_len, to_copy)
}
} else {
let len_avail = length_buffer.available_mut();
let to_copy = available.len().min(len_avail.len());
len_avail[..to_copy].copy_from_slice(&available[..to_copy]);
length_buffer.update_pos_by(to_copy);
(false, to_copy)
};
self.read_buffer.consume(used);
read += used;
if done {
return Ok(Some((
std::mem::take(&mut out_buf),
raw_fd.map(into_raw_fd_i32),
)));
}
}
}
}
pub struct IpcAdvancedStreamResource {
pub read_half: AsyncRefCell<IpcAdvancedStream>,
pub write_half: AsyncRefCell<BiPipeWrite>,
pub cancel: Rc<CancelHandle>,
pub queued_bytes: AtomicUsize,
pub ref_tracker: IpcRefTracker,
}
impl IpcAdvancedStreamResource {
pub fn new(
stream: i64,
ref_tracker: IpcRefTracker,
) -> Result<Self, std::io::Error> {
let (read_half, write_half) = BiPipe::from_raw(stream as _)?.split();
Ok(Self {
read_half: AsyncRefCell::new(IpcAdvancedStream::new(read_half)),
write_half: AsyncRefCell::new(write_half),
cancel: Default::default(),
queued_bytes: Default::default(),
ref_tracker,
})
}
pub async fn write_msg_bytes(
self: Rc<Self>,
msg: &[u8],
raw_fd: Option<i32>,
) -> Result<(), io::Error> {
let mut write_half = RcRef::map(self, |r| &r.write_half).borrow_mut().await;
write_with_optional_fd(&mut write_half, msg, raw_fd).await
}
}
impl deno_core::Resource for IpcAdvancedStreamResource {
fn close(self: Rc<Self>) {
self.cancel.cancel();
}
}
pub struct IpcJsonStream {
pipe: BiPipeRead,
buffer: Vec<u8>,
read_buffer: ReadBuffer,
}
impl IpcJsonStream {
fn new(pipe: BiPipeRead) -> Self {
Self {
pipe,
buffer: Vec::with_capacity(INITIAL_CAPACITY),
read_buffer: ReadBuffer::new(),
}
}
pub async fn read_msg(
&mut self,
) -> Result<Option<serde_json::Value>, IpcJsonStreamError> {
Ok(self.read_msg_and_raw_fd().await?.map(|(msg, _)| msg))
}
pub async fn read_msg_and_raw_fd(
&mut self,
) -> Result<Option<(serde_json::Value, Option<i32>)>, IpcJsonStreamError> {
let mut read = 0usize;
let mut raw_fd = None;
let mut json = None;
loop {
if self.read_buffer.needs_fill() {
self
.read_buffer
.fill_from_pipe(&mut self.pipe)
.await
.map_err(IpcJsonStreamError::Io)?;
}
if let Some(fd) = self.read_buffer.take_raw_fd() {
debug_assert!(raw_fd.is_none());
if raw_fd.is_none() {
raw_fd = Some(fd);
}
}
let available = self.read_buffer.available_mut();
if available.is_empty() {
if read == 0 {
return Ok(None);
} else {
return Err(IpcJsonStreamError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"ipc stream closed while reading message",
)));
}
}
let (done, used) = if let Some(i) = memchr(b'\n', available) {
if read == 0 {
json.replace(
serde_json::from_slice(&available[..i + 1])
.map_err(IpcJsonStreamError::Json)?,
);
} else {
self.buffer.extend_from_slice(&available[..=i]);
}
(true, i + 1)
} else {
self.buffer.extend_from_slice(available);
(false, available.len())
};
self.read_buffer.consume(used);
read += used;
if done {
let json = match json {
Some(v) => v,
None => serde_json::from_slice(&self.buffer[..read])
.map_err(IpcJsonStreamError::Json)?,
};
unsafe {
self.buffer.set_len(0);
}
return Ok(Some((json, raw_fd.map(into_raw_fd_i32))));
}
}
}
}
#[allow(clippy::disallowed_methods, reason = "test code")]
#[allow(clippy::print_stdout, reason = "test code")]
#[cfg(test)]
mod tests {
use std::rc::Rc;
use deno_core::RcRef;
use deno_core::serde_json::json;
use super::IpcJsonStreamResource;
#[allow(clippy::unused_async, reason = "async on windows")]
#[cfg(unix)]
pub async fn pair() -> (Rc<IpcJsonStreamResource>, tokio::net::UnixStream) {
let (a, b) = tokio::net::UnixStream::pair().unwrap();
let a = Rc::new(IpcJsonStreamResource::from_stream(
a,
super::IpcRefTracker::new_test(),
));
(a, b)
}
#[cfg(windows)]
pub async fn pair() -> (
Rc<IpcJsonStreamResource>,
tokio::net::windows::named_pipe::NamedPipeServer,
) {
use tokio::net::windows::named_pipe::ClientOptions;
use tokio::net::windows::named_pipe::ServerOptions;
let name =
format!(r"\\.\pipe\deno-named-pipe-test-{}", rand::random::<u32>());
let server = ServerOptions::new().create(name.clone()).unwrap();
let client = ClientOptions::new().open(name).unwrap();
server.connect().await.unwrap();
let client = Rc::new(IpcJsonStreamResource::from_stream(
client,
super::IpcRefTracker::new_test(),
));
(client, server)
}
#[tokio::test]
async fn bench_ipc() -> Result<(), Box<dyn std::error::Error>> {
if std::env::var_os("BENCH_IPC_DENO").is_none() {
return Ok(());
}
let (ipc, mut fd2) = pair().await;
let child = tokio::spawn(async move {
use tokio::io::AsyncWriteExt;
let size = 1024 * 1024;
let stri = "x".repeat(size);
let data = format!("\"{}\"\n", stri);
for _ in 0..100 {
fd2.write_all(data.as_bytes()).await?;
}
Ok::<_, std::io::Error>(())
});
let start = std::time::Instant::now();
let mut bytes = 0;
let mut ipc = RcRef::map(ipc, |r| &r.read_half).borrow_mut().await;
loop {
let Some(msgs) = ipc.read_msg().await? else {
break;
};
bytes += msgs.as_str().unwrap().len();
if start.elapsed().as_secs() > 5 {
break;
}
}
let elapsed = start.elapsed();
let mb = bytes as f64 / 1024.0 / 1024.0;
println!("{} mb/s", mb / elapsed.as_secs_f64());
child.await??;
Ok(())
}
#[tokio::test]
async fn unix_ipc_json() -> Result<(), Box<dyn std::error::Error>> {
let (ipc, mut fd2) = pair().await;
let child = tokio::spawn(async move {
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
const EXPECTED: &[u8] = b"\"hello\"\n";
let mut buf = [0u8; EXPECTED.len()];
let n = fd2.read_exact(&mut buf).await?;
assert_eq!(&buf[..n], EXPECTED);
fd2.write_all(b"\"world\"\n").await?;
Ok::<_, std::io::Error>(())
});
ipc
.clone()
.write_msg_bytes(&json_to_bytes(json!("hello")), None)
.await?;
let mut ipc = RcRef::map(ipc, |r| &r.read_half).borrow_mut().await;
let msgs = ipc.read_msg().await?.unwrap();
assert_eq!(msgs, json!("world"));
child.await??;
Ok(())
}
fn json_to_bytes(v: deno_core::serde_json::Value) -> Vec<u8> {
let mut buf = deno_core::serde_json::to_vec(&v).unwrap();
buf.push(b'\n');
buf
}
#[tokio::test]
async fn unix_ipc_json_multi() -> Result<(), Box<dyn std::error::Error>> {
let (ipc, mut fd2) = pair().await;
let child = tokio::spawn(async move {
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
const EXPECTED: &[u8] = b"\"hello\"\n\"world\"\n";
let mut buf = [0u8; EXPECTED.len()];
let n = fd2.read_exact(&mut buf).await?;
assert_eq!(&buf[..n], EXPECTED);
fd2.write_all(b"\"foo\"\n\"bar\"\n").await?;
Ok::<_, std::io::Error>(())
});
ipc
.clone()
.write_msg_bytes(&json_to_bytes(json!("hello")), None)
.await?;
ipc
.clone()
.write_msg_bytes(&json_to_bytes(json!("world")), None)
.await?;
let mut ipc = RcRef::map(ipc, |r| &r.read_half).borrow_mut().await;
let msgs = ipc.read_msg().await?.unwrap();
assert_eq!(msgs, json!("foo"));
child.await??;
Ok(())
}
#[tokio::test]
async fn unix_ipc_json_invalid() -> Result<(), Box<dyn std::error::Error>> {
let (ipc, mut fd2) = pair().await;
let child = tokio::spawn(async move {
tokio::io::AsyncWriteExt::write_all(&mut fd2, b"\n\n").await?;
Ok::<_, std::io::Error>(())
});
let mut ipc = RcRef::map(ipc, |r| &r.read_half).borrow_mut().await;
let _err = ipc.read_msg().await.unwrap_err();
child.await??;
Ok(())
}
#[test]
fn memchr() {
let str = b"hello world";
assert_eq!(super::memchr(b'h', str), Some(0));
assert_eq!(super::memchr(b'w', str), Some(6));
assert_eq!(super::memchr(b'd', str), Some(10));
assert_eq!(super::memchr(b'x', str), None);
let empty = b"";
assert_eq!(super::memchr(b'\n', empty), None);
}
}