use core::mem::size_of;
use bun_event_loop::EventLoopHandle;
use bun_io::Loop as AsyncLoop;
#[cfg(windows)]
use bun_io::pipe_writer::BaseWindowsPipeWriter as _;
use bun_io::{BufferedWriter, WriteStatus};
use bun_ptr::{IntrusiveRc, RawSlice, RefCount};
use bun_sys;
use crate::process::StdioKind;
use crate::subprocess::{Source, StdioResult};
bun_output::declare_scope!(StaticPipeWriter, hidden);
pub trait StaticPipeWriterProcess {
const POLL_OWNER_TAG: bun_io::PollTag;
unsafe fn on_close_io(this: *mut Self, kind: StdioKind);
}
#[derive(bun_ptr::RefCounted)]
pub struct StaticPipeWriter<P: StaticPipeWriterProcess> {
pub ref_count: RefCount<Self>,
pub writer: IOWriter<P>,
pub stdio_result: StdioResult,
pub source: Source,
pub process: *mut P,
pub event_loop: EventLoopHandle,
pub started: bool,
pub buffer: RawSlice<u8>,
}
pub type IOWriter<P> = BufferedWriter<StaticPipeWriter<P>>;
pub type Poll<P> = IOWriter<P>;
bun_io::impl_buffered_writer_parent! {
for<P: StaticPipeWriterProcess> StaticPipeWriter<P>;
poll_tag = P::POLL_OWNER_TAG,
borrow = mut,
on_write = on_write,
on_error = on_error,
on_close = on_close,
get_buffer = |this| &*(*this).buffer.as_ptr(),
event_loop = |this| (*this).io_evtloop(),
uv_loop = |this| (*this).event_loop.uv_loop(),
ref_ = |this| RefCount::<Self>::ref_(this),
deref = |this| RefCount::<Self>::deref(this),
win_on_write_guard = |_this| (),
}
impl<P: StaticPipeWriterProcess> StaticPipeWriter<P> {
#[inline]
fn io_evtloop(&self) -> bun_io::EventLoopHandle {
self.event_loop.as_event_loop_ctx()
}
pub fn update_ref(&mut self, add: bool) {
self.writer.update_ref(self.io_evtloop(), add);
}
pub fn get_buffer(&self) -> &[u8] {
self.buffer.slice()
}
pub fn close(&mut self) {
bun_output::scoped_log!(
StaticPipeWriter,
"StaticPipeWriter(0x{:x}) close()",
std::ptr::from_ref(self) as usize
);
self.writer.close();
}
pub fn flush(&mut self) {
if self.buffer.len() > 0 {
self.writer.write();
}
}
pub fn create(
event_loop: EventLoopHandle,
subprocess: *mut P,
result: StdioResult,
source: Source,
) -> IntrusiveRc<Self> {
let this = bun_core::heap::into_raw(Box::new(Self {
ref_count: RefCount::init(),
writer: IOWriter::<P>::default(),
stdio_result: result,
source,
process: subprocess,
event_loop,
started: false,
buffer: RawSlice::EMPTY,
}));
let this_ref = unsafe { &mut *this };
#[cfg(windows)]
{
use crate::process::WindowsStdioResult;
match core::mem::replace(&mut this_ref.stdio_result, WindowsStdioResult::Unavailable) {
WindowsStdioResult::Buffer(pipe) => {
unsafe { this_ref.writer.set_pipe(bun_core::heap::into_raw(pipe)) };
}
WindowsStdioResult::BufferFd(_) | WindowsStdioResult::Unavailable => {
unreachable!("StaticPipeWriter stdin requires WindowsStdioResult::Buffer");
}
}
}
this_ref.writer.set_parent(this);
unsafe { IntrusiveRc::from_raw(this) }
}
pub fn start(&mut self) -> bun_sys::Result<()> {
bun_output::scoped_log!(
StaticPipeWriter,
"StaticPipeWriter(0x{:x}) start()",
std::ptr::from_ref(self) as usize
);
unsafe { RefCount::<Self>::ref_(std::ptr::from_mut::<Self>(self)) };
self.buffer = RawSlice::new(self.source.slice());
#[cfg(windows)]
{
let r = self.writer.start_with_current_pipe();
self.started = r.is_ok();
if r.is_err() {
unsafe { RefCount::<Self>::deref(std::ptr::from_mut::<Self>(self)) };
}
return r;
}
#[cfg(not(windows))]
{
match self.writer.start(self.stdio_result.unwrap(), true) {
bun_sys::Result::Err(err) => {
unsafe { RefCount::<Self>::deref(std::ptr::from_mut::<Self>(self)) };
bun_sys::Result::Err(err)
}
bun_sys::Result::Ok(()) => {
self.started = true;
#[cfg(unix)]
{
if let Some(poll) = self.writer.handle.get_poll() {
poll.set_flag(bun_io::FilePollFlag::Socket);
}
}
bun_sys::Result::Ok(())
}
}
}
}
pub fn on_write(&mut self, amount: usize, status: WriteStatus) {
bun_output::scoped_log!(
StaticPipeWriter,
"StaticPipeWriter(0x{:x}) onWrite(amount={} {})",
std::ptr::from_ref(self) as usize,
amount,
match status {
WriteStatus::EndOfFile => "end_of_file",
WriteStatus::Drained => "drained",
WriteStatus::Pending => "pending",
}
);
let len = self.buffer.len();
self.buffer = RawSlice::new(&self.buffer.slice()[amount.min(len)..]);
if status == WriteStatus::EndOfFile {
if core::mem::replace(&mut self.started, false) {
unsafe { RefCount::<Self>::deref(std::ptr::from_mut::<Self>(self)) };
}
return;
}
if self.buffer.is_empty() {
let release_start_ref = core::mem::replace(&mut self.started, false);
self.writer.close();
if release_start_ref {
unsafe { RefCount::<Self>::deref(std::ptr::from_mut::<Self>(self)) };
}
}
}
pub fn on_error(&mut self, err: &bun_sys::Error) {
bun_output::scoped_log!(
StaticPipeWriter,
"StaticPipeWriter(0x{:x}) onError(err={})",
std::ptr::from_ref(self) as usize,
err
);
self.buffer = RawSlice::EMPTY;
self.source.detach();
}
pub fn on_close(&mut self) {
bun_output::scoped_log!(
StaticPipeWriter,
"StaticPipeWriter(0x{:x}) onClose()",
std::ptr::from_ref(self) as usize
);
let release_start_ref = core::mem::replace(&mut self.started, false);
self.buffer = RawSlice::EMPTY;
self.source.detach();
unsafe { P::on_close_io(self.process, StdioKind::Stdin) };
if release_start_ref {
unsafe { RefCount::<Self>::deref(std::ptr::from_mut::<Self>(self)) };
}
}
pub fn memory_cost(&self) -> usize {
size_of::<Self>() + self.source.memory_cost() + self.writer.memory_cost()
}
pub fn loop_(&self) -> *mut AsyncLoop {
self.event_loop.native_loop()
}
pub fn watch(&mut self) {
if self.buffer.len() > 0 {
self.writer.watch();
}
}
pub fn event_loop(&self) -> EventLoopHandle {
self.event_loop
}
}
impl<P: StaticPipeWriterProcess> Drop for StaticPipeWriter<P> {
fn drop(&mut self) {
self.writer.end();
self.source.detach();
}
}