Skip to content

feat: Add AsyncOpenFile infrastructure and migrate builtins to async I/O#1069

Open
lu-zero wants to merge 3 commits intoreubeno:mainfrom
lu-zero:async-openfile
Open

feat: Add AsyncOpenFile infrastructure and migrate builtins to async I/O#1069
lu-zero wants to merge 3 commits intoreubeno:mainfrom
lu-zero:async-openfile

Conversation

@lu-zero
Copy link
Copy Markdown
Contributor

@lu-zero lu-zero commented Mar 21, 2026

Summary

This PR introduces async file descriptor abstractions (AsyncOpenFile, AsyncOpenFiles, AsyncStream) and migrates most builtins to use async I/O for better performance with piped/redirected I/O.

Infrastructure Changes

  • AsyncOpenFile: Wraps a tokio::fs::File for async read/write operations
  • AsyncOpenFiles: Wrapper for managing async file descriptors
  • AsyncStream: Trait for async stream operations
  • Added stdin_async(), stdout_async(), stderr_async() methods to ExecutionParameters and ExecutionContext

Builtins Migrated to Async I/O

Builtin Change
mapfile Async read for non-terminal input
echo Async write
read Async read with timeout support for non-terminal input
printf Async write
help Async write
fc Async write for list output
jobs Async write
type/alias Async write
hash Async write
enable/dirs Async write
caller/export Async write
command Async write
kill Async write for signal list
bg/cd Async write
pwd/shopt Async write
set/unalias Async write
ulimit/umask Async write
times/trap Async write
wait/suspend/let/return/test Async write/stderr
fg Async write
history/declare/complete Async write
bind Async write

Pattern Used

  1. Buffer output to Vec<u8>
  2. Write async via context.stdout_async() if available
  3. Fall back to blocking I/O for terminals (blocking I/O required for terminal mode setup)
  4. Stderr continues using blocking I/O (no async needed for error messages)

Testing

  • All 2218 integration tests pass
  • All quick CI checks pass

Notes

  • Terminal handling continues to use blocking I/O since terminal mode setup uses blocking APIs
  • The coproc async pipe infrastructure is not included in this PR as it has additional dependencies

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 21, 2026

Public API changes for crate: brush-core

Removed items

-pub fn brush_core::commands::ExecutionContext<'_, SE>::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>
-pub fn brush_core::commands::ExecutionContext<'_, SE>::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>
-pub fn brush_core::commands::ExecutionContext<'_, SE>::stderr(&self) -> impl std::io::Write + 'static
-pub fn brush_core::commands::ExecutionContext<'_, SE>::stderr(&self) -> impl std::io::Write + 'static
-pub fn brush_core::commands::ExecutionContext<'_, SE>::stdin(&self) -> impl std::io::Read + 'static
-pub fn brush_core::commands::ExecutionContext<'_, SE>::stdin(&self) -> impl std::io::Read + 'static
-pub fn brush_core::commands::ExecutionContext<'_, SE>::stdout(&self) -> impl std::io::Write + 'static
-pub fn brush_core::commands::ExecutionContext<'_, SE>::stdout(&self) -> impl std::io::Write + 'static
-pub fn brush_core::commands::ExecutionContext<'_, SE>::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::OpenFile>
-pub fn brush_core::commands::ExecutionContext<'_, SE>::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::OpenFile>
-impl brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::is_terminal(&self) -> bool
-pub fn brush_core::openfiles::OpenFile::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
-pub fn brush_core::openfiles::OpenFile::try_clone(&self) -> core::result::Result<Self, std::io::error::Error>
-impl core::clone::Clone for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::clone(&self) -> Self
-impl core::convert::From<brush_core::openfiles::OpenFile> for std::process::Stdio
-pub fn std::process::Stdio::from(open_file: brush_core::openfiles::OpenFile) -> Self
-impl core::convert::From<std::fs::File> for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::from(file: std::fs::File) -> Self
-impl core::convert::From<std::io::pipe::PipeReader> for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::from(reader: std::io::pipe::PipeReader) -> Self
-impl core::convert::From<std::io::pipe::PipeWriter> for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::from(writer: std::io::pipe::PipeWriter) -> Self
-impl core::convert::From<std::io::stdio::Stderr> for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::from(stderr: std::io::stdio::Stderr) -> Self
-impl core::convert::From<std::io::stdio::Stdin> for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::from(stdin: std::io::stdio::Stdin) -> Self
-impl core::convert::From<std::io::stdio::Stdout> for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::from(stdout: std::io::stdio::Stdout) -> Self
-impl core::fmt::Display for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-impl std::io::Read for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::read(&mut self, buf: &mut [u8]) -> std::io::error::Result<usize>
-impl std::io::Write for brush_core::openfiles::OpenFile
-pub fn brush_core::openfiles::OpenFile::flush(&mut self) -> std::io::error::Result<()>
-pub fn brush_core::openfiles::OpenFile::write(&mut self, buf: &[u8]) -> std::io::error::Result<usize>
-impl brush_core::openfiles::OpenFiles
-pub const brush_core::openfiles::OpenFiles::STDERR_FD: brush_core::ShellFd
-pub const brush_core::openfiles::OpenFiles::STDIN_FD: brush_core::ShellFd
-pub const brush_core::openfiles::OpenFiles::STDOUT_FD: brush_core::ShellFd
-pub fn brush_core::openfiles::OpenFiles::add(&mut self, file: brush_core::openfiles::OpenFile) -> core::result::Result<brush_core::ShellFd, brush_core::error::Error>
-pub fn brush_core::openfiles::OpenFiles::contains_fd(&self, fd: brush_core::ShellFd) -> bool
-pub fn brush_core::openfiles::OpenFiles::fd_entry(&self, fd: brush_core::ShellFd) -> brush_core::openfiles::OpenFileEntry<'_>
-pub fn brush_core::openfiles::OpenFiles::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, &brush_core::openfiles::OpenFile)>
-pub fn brush_core::openfiles::OpenFiles::remove_fd(&mut self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::OpenFile>
-pub fn brush_core::openfiles::OpenFiles::set_fd(&mut self, fd: brush_core::ShellFd, file: brush_core::openfiles::OpenFile) -> core::option::Option<brush_core::openfiles::OpenFile>
-pub fn brush_core::openfiles::OpenFiles::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<&brush_core::openfiles::OpenFile>
-pub fn brush_core::openfiles::OpenFiles::try_stderr(&self) -> core::option::Option<&brush_core::openfiles::OpenFile>
-pub fn brush_core::openfiles::OpenFiles::try_stdin(&self) -> core::option::Option<&brush_core::openfiles::OpenFile>
-pub fn brush_core::openfiles::OpenFiles::try_stdout(&self) -> core::option::Option<&brush_core::openfiles::OpenFile>
-pub fn brush_core::openfiles::OpenFiles::update_from(&mut self, files: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>)
-impl<I> core::convert::From<I> for brush_core::openfiles::OpenFiles where I: core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>
-pub fn brush_core::openfiles::OpenFiles::from(iter: I) -> Self
-pub fn brush_core::ExecutionParameters::iter_fds(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>
-pub fn brush_core::ExecutionParameters::set_fd(&mut self, fd: brush_core::ShellFd, file: brush_core::openfiles::OpenFile)
-pub fn brush_core::ExecutionParameters::stderr(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> impl std::io::Write + 'static
-pub fn brush_core::ExecutionParameters::stdin(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> impl std::io::Read + 'static
-pub fn brush_core::ExecutionParameters::stdout(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> impl std::io::Write + 'static
-pub fn brush_core::ExecutionParameters::try_fd(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::OpenFile>
-pub fn brush_core::ExecutionParameters::try_stderr(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> core::option::Option<brush_core::openfiles::OpenFile>
-pub fn brush_core::ExecutionParameters::try_stdin(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> core::option::Option<brush_core::openfiles::OpenFile>
-pub fn brush_core::ExecutionParameters::try_stdout(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> core::option::Option<brush_core::openfiles::OpenFile>

Added items

+pub fn brush_core::commands::ExecutionContext<'_, SE>::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::stderr(&self) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::stderr(&self) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::stdin(&self) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::stdin(&self) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::stdout(&self) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::stdout(&self) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::commands::ExecutionContext<'_, SE>::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub async fn brush_core::commands::ExecutionContext<'_, SE>::write_error(&self, msg: impl core::fmt::Display)
+pub async fn brush_core::commands::ExecutionContext<'_, SE>::write_error(&self, msg: impl core::fmt::Display)
+pub async fn brush_core::commands::ExecutionContext<'_, SE>::write_stderr(&self, msg: impl core::fmt::Display) -> std::io::error::Result<()>
+pub async fn brush_core::commands::ExecutionContext<'_, SE>::write_stderr(&self, msg: impl core::fmt::Display) -> std::io::error::Result<()>
+pub async fn brush_core::commands::ExecutionContext<'_, SE>::write_stdout(&self, msg: impl core::fmt::Display) -> std::io::error::Result<()>
+pub async fn brush_core::commands::ExecutionContext<'_, SE>::write_stdout(&self, msg: impl core::fmt::Display) -> std::io::error::Result<()>
+pub mod brush_core::openfiles::async_file
+pub enum brush_core::openfiles::async_file::AsyncOpenFile
+pub brush_core::openfiles::async_file::AsyncOpenFile::Broken(alloc::string::String)
+pub brush_core::openfiles::async_file::AsyncOpenFile::File(tokio::fs::file::File)
+pub brush_core::openfiles::async_file::AsyncOpenFile::PipeReader(tokio::net::unix::pipe::Receiver)
+pub brush_core::openfiles::async_file::AsyncOpenFile::PipeWriter(tokio::net::unix::pipe::Sender)
+pub brush_core::openfiles::async_file::AsyncOpenFile::Stderr(tokio::io::stderr::Stderr)
+pub brush_core::openfiles::async_file::AsyncOpenFile::Stdin(tokio::io::stdin::Stdin)
+pub brush_core::openfiles::async_file::AsyncOpenFile::Stdout(tokio::io::stdout::Stdout)
+pub brush_core::openfiles::async_file::AsyncOpenFile::Stream(alloc::boxed::Box<dyn brush_core::openfiles::async_file::AsyncStream>)
+impl brush_core::openfiles::async_file::AsyncOpenFile
+impl brush_core::openfiles::async_file::AsyncOpenFile
+impl brush_core::openfiles::async_file::AsyncOpenFile
+impl brush_core::openfiles::async_file::AsyncOpenFile
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::flush(&mut self) -> std::io::error::Result<()>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::flush(&mut self) -> std::io::error::Result<()>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::is_dir(&self) -> bool
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::is_dir(&self) -> bool
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::is_terminal(&self) -> bool
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::is_terminal(&self) -> bool
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::read(&mut self, buf: &mut [u8]) -> std::io::error::Result<usize>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::read(&mut self, buf: &mut [u8]) -> std::io::error::Result<usize>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::read_to_string(&mut self) -> std::io::error::Result<alloc::string::String>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::read_to_string(&mut self) -> std::io::error::Result<alloc::string::String>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::try_clone_to_owned(&self) -> core::result::Result<std::os::fd::owned::OwnedFd, brush_core::error::Error>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::try_clone_to_owned(&self) -> core::result::Result<std::os::fd::owned::OwnedFd, brush_core::error::Error>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::write(&mut self, buf: &[u8]) -> std::io::error::Result<usize>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::write(&mut self, buf: &[u8]) -> std::io::error::Result<usize>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::write_all(&mut self, buf: &[u8]) -> std::io::error::Result<()>
+pub async fn brush_core::openfiles::async_file::AsyncOpenFile::write_all(&mut self, buf: &[u8]) -> std::io::error::Result<()>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from_pipe_reader(reader: std::io::pipe::PipeReader) -> std::io::error::Result<Self>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from_pipe_reader(reader: std::io::pipe::PipeReader) -> std::io::error::Result<Self>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from_pipe_writer(writer: std::io::pipe::PipeWriter) -> std::io::error::Result<Self>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from_pipe_writer(writer: std::io::pipe::PipeWriter) -> std::io::error::Result<Self>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from_std_file(file: std::fs::File) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from_std_file(file: std::fs::File) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::try_clone(&self) -> std::io::error::Result<Self>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::try_clone(&self) -> std::io::error::Result<Self>
+impl core::clone::Clone for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::clone::Clone for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::clone(&self) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::clone(&self) -> Self
+impl core::convert::From<brush_core::openfiles::async_file::AsyncOpenFile> for std::process::Stdio
+impl core::convert::From<brush_core::openfiles::async_file::AsyncOpenFile> for std::process::Stdio
+pub fn std::process::Stdio::from(file: brush_core::openfiles::async_file::AsyncOpenFile) -> Self
+pub fn std::process::Stdio::from(file: brush_core::openfiles::async_file::AsyncOpenFile) -> Self
+impl core::convert::From<brush_core::openfiles::blocking::File> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<brush_core::openfiles::blocking::File> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<brush_core::openfiles::blocking::File> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<brush_core::openfiles::blocking::File> for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(file: brush_core::openfiles::blocking::File) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(file: brush_core::openfiles::blocking::File) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(file: brush_core::openfiles::blocking::File) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(file: brush_core::openfiles::blocking::File) -> Self
+impl core::convert::From<std::fs::File> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<std::fs::File> for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(file: std::fs::File) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(file: std::fs::File) -> Self
+impl core::convert::From<std::io::pipe::PipeReader> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<std::io::pipe::PipeReader> for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(reader: std::io::pipe::PipeReader) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(reader: std::io::pipe::PipeReader) -> Self
+impl core::convert::From<std::io::pipe::PipeWriter> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<std::io::pipe::PipeWriter> for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(writer: std::io::pipe::PipeWriter) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(writer: std::io::pipe::PipeWriter) -> Self
+impl core::convert::From<std::io::stdio::Stderr> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<std::io::stdio::Stderr> for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(_stderr: std::io::stdio::Stderr) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(_stderr: std::io::stdio::Stderr) -> Self
+impl core::convert::From<std::io::stdio::Stdin> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<std::io::stdio::Stdin> for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(_stdin: std::io::stdio::Stdin) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(_stdin: std::io::stdio::Stdin) -> Self
+impl core::convert::From<std::io::stdio::Stdout> for brush_core::openfiles::async_file::AsyncOpenFile
+impl core::convert::From<std::io::stdio::Stdout> for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(_stdout: std::io::stdio::Stdout) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::from(_stdout: std::io::stdio::Stdout) -> Self
+impl tokio::io::async_read::AsyncRead for brush_core::openfiles::async_file::AsyncOpenFile
+impl tokio::io::async_read::AsyncRead for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_read(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>, buf: &mut tokio::io::read_buf::ReadBuf<'_>) -> core::task::poll::Poll<std::io::error::Result<()>>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_read(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>, buf: &mut tokio::io::read_buf::ReadBuf<'_>) -> core::task::poll::Poll<std::io::error::Result<()>>
+impl tokio::io::async_write::AsyncWrite for brush_core::openfiles::async_file::AsyncOpenFile
+impl tokio::io::async_write::AsyncWrite for brush_core::openfiles::async_file::AsyncOpenFile
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_flush(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>) -> core::task::poll::Poll<std::io::error::Result<()>>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_flush(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>) -> core::task::poll::Poll<std::io::error::Result<()>>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_shutdown(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>) -> core::task::poll::Poll<std::io::error::Result<()>>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_shutdown(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>) -> core::task::poll::Poll<std::io::error::Result<()>>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_write(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>, buf: &[u8]) -> core::task::poll::Poll<std::io::error::Result<usize>>
+pub fn brush_core::openfiles::async_file::AsyncOpenFile::poll_write(self: core::pin::Pin<&mut Self>, cx: &mut core::task::wake::Context<'_>, buf: &[u8]) -> core::task::poll::Poll<std::io::error::Result<usize>>
+pub enum brush_core::openfiles::async_file::AsyncOpenFileEntry<'a>
+pub brush_core::openfiles::async_file::AsyncOpenFileEntry::NotPresent
+pub brush_core::openfiles::async_file::AsyncOpenFileEntry::NotSpecified
+pub brush_core::openfiles::async_file::AsyncOpenFileEntry::Open(&'a brush_core::openfiles::async_file::AsyncOpenFile)
+pub struct brush_core::openfiles::async_file::AsyncOpenFiles
+impl brush_core::openfiles::async_file::AsyncOpenFiles
+impl brush_core::openfiles::async_file::AsyncOpenFiles
+pub const brush_core::openfiles::async_file::AsyncOpenFiles::STDERR_FD: brush_core::ShellFd
+pub const brush_core::openfiles::async_file::AsyncOpenFiles::STDERR_FD: brush_core::ShellFd
+pub const brush_core::openfiles::async_file::AsyncOpenFiles::STDIN_FD: brush_core::ShellFd
+pub const brush_core::openfiles::async_file::AsyncOpenFiles::STDIN_FD: brush_core::ShellFd
+pub const brush_core::openfiles::async_file::AsyncOpenFiles::STDOUT_FD: brush_core::ShellFd
+pub const brush_core::openfiles::async_file::AsyncOpenFiles::STDOUT_FD: brush_core::ShellFd
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::add(&mut self, file: brush_core::openfiles::async_file::AsyncOpenFile) -> core::result::Result<brush_core::ShellFd, brush_core::error::Error>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::add(&mut self, file: brush_core::openfiles::async_file::AsyncOpenFile) -> core::result::Result<brush_core::ShellFd, brush_core::error::Error>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::contains_fd(&self, fd: brush_core::ShellFd) -> bool
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::contains_fd(&self, fd: brush_core::ShellFd) -> bool
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::fd_entry(&self, fd: brush_core::ShellFd) -> brush_core::openfiles::async_file::AsyncOpenFileEntry<'_>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::fd_entry(&self, fd: brush_core::ShellFd) -> brush_core::openfiles::async_file::AsyncOpenFileEntry<'_>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, &brush_core::openfiles::async_file::AsyncOpenFile)>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, &brush_core::openfiles::async_file::AsyncOpenFile)>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::new() -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::new() -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::remove_fd(&mut self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::remove_fd(&mut self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::set_fd(&mut self, fd: brush_core::ShellFd, file: brush_core::openfiles::async_file::AsyncOpenFile) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::set_fd(&mut self, fd: brush_core::ShellFd, file: brush_core::openfiles::async_file::AsyncOpenFile) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_stderr(&self) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_stderr(&self) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_stdin(&self) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_stdin(&self) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_stdout(&self) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::try_stdout(&self) -> core::option::Option<&brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::update_from(&mut self, files: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>)
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::update_from(&mut self, files: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>)
+impl core::convert::From<brush_core::openfiles::blocking::Files> for brush_core::openfiles::async_file::AsyncOpenFiles
+impl core::convert::From<brush_core::openfiles::blocking::Files> for brush_core::openfiles::async_file::AsyncOpenFiles
+impl core::convert::From<brush_core::openfiles::blocking::Files> for brush_core::openfiles::async_file::AsyncOpenFiles
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::from(open_files: brush_core::openfiles::blocking::Files) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::from(open_files: brush_core::openfiles::blocking::Files) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::from(open_files: brush_core::openfiles::blocking::Files) -> Self
+impl<I> core::convert::From<I> for brush_core::openfiles::async_file::AsyncOpenFiles where I: core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>
+impl<I> core::convert::From<I> for brush_core::openfiles::async_file::AsyncOpenFiles where I: core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::from(iter: I) -> Self
+pub fn brush_core::openfiles::async_file::AsyncOpenFiles::from(iter: I) -> Self
+pub trait brush_core::openfiles::async_file::AsyncStream: tokio::io::async_read::AsyncRead + tokio::io::async_write::AsyncWrite + core::marker::Send + core::marker::Sync + core::marker::Unpin
+pub fn brush_core::openfiles::async_file::AsyncStream::clone_box(&self) -> alloc::boxed::Box<dyn brush_core::openfiles::async_file::AsyncStream>
+pub fn brush_core::openfiles::async_file::AsyncStream::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
+pub fn brush_core::openfiles::async_file::AsyncStream::try_clone_to_owned(&self) -> core::result::Result<std::os::fd::owned::OwnedFd, brush_core::error::Error>
+pub mod brush_core::openfiles::blocking
+pub enum brush_core::openfiles::blocking::File
+pub brush_core::openfiles::blocking::File::File(std::fs::File)
+pub brush_core::openfiles::blocking::File::PipeReader(std::io::pipe::PipeReader)
+pub brush_core::openfiles::blocking::File::PipeWriter(std::io::pipe::PipeWriter)
+pub brush_core::openfiles::blocking::File::Stderr(std::io::stdio::Stderr)
+pub brush_core::openfiles::blocking::File::Stdin(std::io::stdio::Stdin)
+pub brush_core::openfiles::blocking::File::Stdout(std::io::stdio::Stdout)
+pub brush_core::openfiles::blocking::File::Stream(alloc::boxed::Box<dyn brush_core::openfiles::blocking::BlockingStream>)
+impl brush_core::openfiles::blocking::File
+impl brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::is_terminal(&self) -> bool
+pub fn brush_core::openfiles::blocking::File::is_terminal(&self) -> bool
+pub fn brush_core::openfiles::blocking::File::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
+pub fn brush_core::openfiles::blocking::File::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
+pub fn brush_core::openfiles::blocking::File::try_clone(&self) -> core::result::Result<Self, std::io::error::Error>
+pub fn brush_core::openfiles::blocking::File::try_clone(&self) -> core::result::Result<Self, std::io::error::Error>
+impl core::clone::Clone for brush_core::openfiles::blocking::File
+impl core::clone::Clone for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::clone(&self) -> Self
+pub fn brush_core::openfiles::blocking::File::clone(&self) -> Self
+impl core::convert::From<brush_core::openfiles::blocking::File> for std::process::Stdio
+impl core::convert::From<brush_core::openfiles::blocking::File> for std::process::Stdio
+pub fn std::process::Stdio::from(open_file: brush_core::openfiles::blocking::File) -> Self
+pub fn std::process::Stdio::from(open_file: brush_core::openfiles::blocking::File) -> Self
+impl core::convert::From<std::fs::File> for brush_core::openfiles::blocking::File
+impl core::convert::From<std::fs::File> for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::from(file: std::fs::File) -> Self
+pub fn brush_core::openfiles::blocking::File::from(file: std::fs::File) -> Self
+impl core::convert::From<std::io::pipe::PipeReader> for brush_core::openfiles::blocking::File
+impl core::convert::From<std::io::pipe::PipeReader> for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::from(reader: std::io::pipe::PipeReader) -> Self
+pub fn brush_core::openfiles::blocking::File::from(reader: std::io::pipe::PipeReader) -> Self
+impl core::convert::From<std::io::pipe::PipeWriter> for brush_core::openfiles::blocking::File
+impl core::convert::From<std::io::pipe::PipeWriter> for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::from(writer: std::io::pipe::PipeWriter) -> Self
+pub fn brush_core::openfiles::blocking::File::from(writer: std::io::pipe::PipeWriter) -> Self
+impl core::convert::From<std::io::stdio::Stderr> for brush_core::openfiles::blocking::File
+impl core::convert::From<std::io::stdio::Stderr> for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::from(stderr: std::io::stdio::Stderr) -> Self
+pub fn brush_core::openfiles::blocking::File::from(stderr: std::io::stdio::Stderr) -> Self
+impl core::convert::From<std::io::stdio::Stdin> for brush_core::openfiles::blocking::File
+impl core::convert::From<std::io::stdio::Stdin> for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::from(stdin: std::io::stdio::Stdin) -> Self
+pub fn brush_core::openfiles::blocking::File::from(stdin: std::io::stdio::Stdin) -> Self
+impl core::convert::From<std::io::stdio::Stdout> for brush_core::openfiles::blocking::File
+impl core::convert::From<std::io::stdio::Stdout> for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::from(stdout: std::io::stdio::Stdout) -> Self
+pub fn brush_core::openfiles::blocking::File::from(stdout: std::io::stdio::Stdout) -> Self
+impl core::fmt::Display for brush_core::openfiles::blocking::File
+impl core::fmt::Display for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn brush_core::openfiles::blocking::File::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+impl std::io::Read for brush_core::openfiles::blocking::File
+impl std::io::Read for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::read(&mut self, buf: &mut [u8]) -> std::io::error::Result<usize>
+pub fn brush_core::openfiles::blocking::File::read(&mut self, buf: &mut [u8]) -> std::io::error::Result<usize>
+impl std::io::Write for brush_core::openfiles::blocking::File
+impl std::io::Write for brush_core::openfiles::blocking::File
+pub fn brush_core::openfiles::blocking::File::flush(&mut self) -> std::io::error::Result<()>
+pub fn brush_core::openfiles::blocking::File::flush(&mut self) -> std::io::error::Result<()>
+pub fn brush_core::openfiles::blocking::File::write(&mut self, buf: &[u8]) -> std::io::error::Result<usize>
+pub fn brush_core::openfiles::blocking::File::write(&mut self, buf: &[u8]) -> std::io::error::Result<usize>
+pub struct brush_core::openfiles::blocking::Files
+impl brush_core::openfiles::blocking::Files
+pub const brush_core::openfiles::blocking::Files::STDERR_FD: brush_core::ShellFd
+pub const brush_core::openfiles::blocking::Files::STDIN_FD: brush_core::ShellFd
+pub const brush_core::openfiles::blocking::Files::STDOUT_FD: brush_core::ShellFd
+pub fn brush_core::openfiles::blocking::Files::add(&mut self, file: brush_core::openfiles::blocking::File) -> core::result::Result<brush_core::ShellFd, brush_core::error::Error>
+pub fn brush_core::openfiles::blocking::Files::iter_fds(&self) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, &brush_core::openfiles::blocking::File)>
+pub fn brush_core::openfiles::blocking::Files::remove_fd(&mut self, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::blocking::File>
+pub fn brush_core::openfiles::blocking::Files::set_fd(&mut self, fd: brush_core::ShellFd, file: brush_core::openfiles::blocking::File) -> core::option::Option<brush_core::openfiles::blocking::File>
+pub fn brush_core::openfiles::blocking::Files::try_fd(&self, fd: brush_core::ShellFd) -> core::option::Option<&brush_core::openfiles::blocking::File>
+pub fn brush_core::openfiles::blocking::Files::try_stderr(&self) -> core::option::Option<&brush_core::openfiles::blocking::File>
+pub fn brush_core::openfiles::blocking::Files::try_stdin(&self) -> core::option::Option<&brush_core::openfiles::blocking::File>
+pub fn brush_core::openfiles::blocking::Files::try_stdout(&self) -> core::option::Option<&brush_core::openfiles::blocking::File>
+impl<I> core::convert::From<I> for brush_core::openfiles::blocking::Files where I: core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::blocking::File)>
+pub fn brush_core::openfiles::blocking::Files::from(iter: I) -> Self
+pub trait brush_core::openfiles::blocking::BlockingStream: std::io::Read + std::io::Write + core::marker::Send + core::marker::Sync
+pub fn brush_core::openfiles::blocking::BlockingStream::clone_box(&self) -> alloc::boxed::Box<dyn brush_core::openfiles::blocking::BlockingStream>
+pub fn brush_core::openfiles::blocking::BlockingStream::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
+pub fn brush_core::openfiles::blocking::BlockingStream::try_clone_to_owned(&self) -> core::result::Result<std::os::fd::owned::OwnedFd, brush_core::error::Error>
+pub enum brush_core::openfiles::File
+pub brush_core::openfiles::File::File(std::fs::File)
+pub brush_core::openfiles::File::PipeReader(std::io::pipe::PipeReader)
+pub brush_core::openfiles::File::PipeWriter(std::io::pipe::PipeWriter)
+pub brush_core::openfiles::File::Stderr(std::io::stdio::Stderr)
+pub brush_core::openfiles::File::Stdin(std::io::stdio::Stdin)
+pub brush_core::openfiles::File::Stdout(std::io::stdio::Stdout)
+pub brush_core::openfiles::File::Stream(alloc::boxed::Box<dyn brush_core::openfiles::blocking::BlockingStream>)
+pub brush_core::openfiles::OpenFile::Broken(alloc::string::String)
+pub trait brush_core::openfiles::BlockingStream: std::io::Read + std::io::Write + core::marker::Send + core::marker::Sync
+pub fn brush_core::openfiles::BlockingStream::clone_box(&self) -> alloc::boxed::Box<dyn brush_core::openfiles::blocking::BlockingStream>
+pub fn brush_core::openfiles::BlockingStream::try_borrow_as_fd(&self) -> core::result::Result<std::os::fd::owned::BorrowedFd<'_>, brush_core::error::Error>
+pub fn brush_core::openfiles::BlockingStream::try_clone_to_owned(&self) -> core::result::Result<std::os::fd::owned::OwnedFd, brush_core::error::Error>
+pub fn brush_core::ExecutionParameters::iter_fds(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>
+pub fn brush_core::ExecutionParameters::set_fd(&mut self, fd: brush_core::ShellFd, file: brush_core::openfiles::async_file::AsyncOpenFile)
+pub fn brush_core::ExecutionParameters::try_fd(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>, fd: brush_core::ShellFd) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::ExecutionParameters::try_stderr(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::ExecutionParameters::try_stdin(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
+pub fn brush_core::ExecutionParameters::try_stdout(&self, shell: &brush_core::Shell<impl brush_core::extensions::ShellExtensions>) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>

Changed items

-pub brush_core::openfiles::OpenFile::File(std::fs::File)
+pub brush_core::openfiles::OpenFile::File(tokio::fs::file::File)
-pub brush_core::openfiles::OpenFile::PipeReader(std::io::pipe::PipeReader)
+pub brush_core::openfiles::OpenFile::PipeReader(tokio::net::unix::pipe::Receiver)
-pub brush_core::openfiles::OpenFile::PipeWriter(std::io::pipe::PipeWriter)
+pub brush_core::openfiles::OpenFile::PipeWriter(tokio::net::unix::pipe::Sender)
-pub brush_core::openfiles::OpenFile::Stderr(std::io::stdio::Stderr)
+pub brush_core::openfiles::OpenFile::Stderr(tokio::io::stderr::Stderr)
-pub brush_core::openfiles::OpenFile::Stdin(std::io::stdio::Stdin)
+pub brush_core::openfiles::OpenFile::Stdin(tokio::io::stdin::Stdin)
-pub brush_core::openfiles::OpenFile::Stdout(std::io::stdio::Stdout)
+pub brush_core::openfiles::OpenFile::Stdout(tokio::io::stdout::Stdout)
-pub brush_core::openfiles::OpenFile::Stream(alloc::boxed::Box<dyn brush_core::openfiles::Stream>)
+pub brush_core::openfiles::OpenFile::Stream(alloc::boxed::Box<dyn brush_core::openfiles::async_file::AsyncStream>)
-pub brush_core::openfiles::OpenFileEntry::Open(&'a brush_core::openfiles::OpenFile)
+pub brush_core::openfiles::OpenFileEntry::Open(&'a brush_core::openfiles::async_file::AsyncOpenFile)
-pub trait brush_core::openfiles::Stream: std::io::Read + std::io::Write + core::marker::Send + core::marker::Sync
+pub trait brush_core::openfiles::Stream: tokio::io::async_read::AsyncRead + tokio::io::async_write::AsyncWrite + core::marker::Send + core::marker::Sync + core::marker::Unpin
-pub fn brush_core::openfiles::Stream::clone_box(&self) -> alloc::boxed::Box<dyn brush_core::openfiles::Stream>
+pub fn brush_core::openfiles::Stream::clone_box(&self) -> alloc::boxed::Box<dyn brush_core::openfiles::async_file::AsyncStream>
-pub fn brush_core::openfiles::null() -> core::result::Result<brush_core::openfiles::OpenFile, brush_core::error::Error>
+pub fn brush_core::openfiles::null() -> core::result::Result<brush_core::openfiles::blocking::File, brush_core::error::Error>
-pub fn brush_core::sys::commands::CommandFdInjectionExt::inject_fds(&mut self, open_files: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>) -> core::result::Result<(), brush_core::error::Error>
+pub fn brush_core::sys::commands::CommandFdInjectionExt::inject_fds(&mut self, open_files: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>) -> core::result::Result<(), brush_core::error::Error>
-pub fn std::process::Command::inject_fds(&mut self, open_files: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>) -> core::result::Result<(), brush_core::error::Error>
+pub fn std::process::Command::inject_fds(&mut self, open_files: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>) -> core::result::Result<(), brush_core::error::Error>
-pub fn brush_core::sys::fd::try_get_file_for_open_fd(fd: std::os::fd::raw::RawFd) -> core::option::Option<brush_core::openfiles::OpenFile>
+pub fn brush_core::sys::fd::try_get_file_for_open_fd(fd: std::os::fd::raw::RawFd) -> core::option::Option<brush_core::openfiles::async_file::AsyncOpenFile>
-pub fn brush_core::sys::fd::try_iter_open_fds() -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>
+pub fn brush_core::sys::fd::try_iter_open_fds() -> impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>
-pub fn brush_core::sys::poll::poll_for_input(file: &brush_core::openfiles::OpenFile, timeout: core::time::Duration) -> std::io::error::Result<bool>
+pub fn brush_core::sys::poll::poll_for_input(file: &brush_core::openfiles::async_file::AsyncOpenFile, timeout: core::time::Duration) -> std::io::error::Result<bool>
-pub fn brush_core::sys::terminal::Config::apply_to_term(&self, file: &brush_core::openfiles::OpenFile) -> core::result::Result<(), brush_core::error::Error>
+pub fn brush_core::sys::terminal::Config::apply_to_term(&self, file: &brush_core::openfiles::async_file::AsyncOpenFile) -> core::result::Result<(), brush_core::error::Error>
-pub fn brush_core::sys::terminal::Config::from_term(file: &brush_core::openfiles::OpenFile) -> core::result::Result<Self, brush_core::error::Error>
+pub fn brush_core::sys::terminal::Config::from_term(file: &brush_core::openfiles::async_file::AsyncOpenFile) -> core::result::Result<Self, brush_core::error::Error>
-pub fn brush_core::terminal::AutoModeGuard::new(file: brush_core::openfiles::OpenFile) -> core::result::Result<Self, brush_core::error::Error>
+pub fn brush_core::terminal::AutoModeGuard::new(file: brush_core::openfiles::async_file::AsyncOpenFile) -> core::result::Result<Self, brush_core::error::Error>
-pub brush_core::CreateOptions::fds: std::collections::hash::map::HashMap<brush_core::ShellFd, brush_core::openfiles::OpenFile>
+pub brush_core::CreateOptions::fds: std::collections::hash::map::HashMap<brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile>
-pub fn brush_core::Shell<SE>::replace_open_files(&mut self, open_fds: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::OpenFile)>)
+pub fn brush_core::Shell<SE>::replace_open_files(&mut self, open_fds: impl core::iter::traits::iterator::Iterator<Item = (brush_core::ShellFd, brush_core::openfiles::async_file::AsyncOpenFile)>)
-pub fn brush_core::Shell<SE>::open_files(&self) -> &brush_core::openfiles::OpenFiles
+pub fn brush_core::Shell<SE>::open_files(&self) -> &brush_core::openfiles::async_file::AsyncOpenFiles
-pub fn brush_core::Shell<SE>::open_files(&self) -> &brush_core::openfiles::OpenFiles
+pub fn brush_core::Shell<SE>::open_files(&self) -> &brush_core::openfiles::async_file::AsyncOpenFiles
-pub fn brush_core::Shell<SE>::open_files(&self) -> &brush_core::openfiles::OpenFiles
+pub fn brush_core::Shell<SE>::open_files(&self) -> &brush_core::openfiles::async_file::AsyncOpenFiles
-pub fn brush_core::Shell<SE>::open_files_mut(&mut self) -> &mut brush_core::openfiles::OpenFiles
+pub fn brush_core::Shell<SE>::open_files_mut(&mut self) -> &mut brush_core::openfiles::async_file::AsyncOpenFiles
-pub fn brush_core::Shell<SE>::open_files_mut(&mut self) -> &mut brush_core::openfiles::OpenFiles
+pub fn brush_core::Shell<SE>::open_files_mut(&mut self) -> &mut brush_core::openfiles::async_file::AsyncOpenFiles
-pub fn brush_core::Shell<SE>::open_files_mut(&mut self) -> &mut brush_core::openfiles::OpenFiles
+pub fn brush_core::Shell<SE>::open_files_mut(&mut self) -> &mut brush_core::openfiles::async_file::AsyncOpenFiles
-pub fn brush_core::Shell<SE>::display_error(&self, file: &mut impl std::io::Write, err: &brush_core::error::Error) -> core::result::Result<(), brush_core::error::Error>
+pub async fn brush_core::Shell<SE>::display_error(&self, err: &brush_core::error::Error, stderr: &mut brush_core::openfiles::async_file::AsyncOpenFile) -> core::result::Result<(), brush_core::error::Error>
-pub fn brush_core::Shell<SE>::stderr(&self) -> impl std::io::Write + 'static
+pub fn brush_core::Shell<SE>::stderr(&self) -> brush_core::openfiles::async_file::AsyncOpenFile
-pub fn brush_core::Shell<SE>::stdout(&self) -> impl std::io::Write + 'static
+pub fn brush_core::Shell<SE>::stdout(&self) -> brush_core::openfiles::async_file::AsyncOpenFile
-pub fn brush_core::ShellState::open_files(&self) -> &brush_core::openfiles::OpenFiles
+pub fn brush_core::ShellState::open_files(&self) -> &brush_core::openfiles::async_file::AsyncOpenFiles
-pub fn brush_core::ShellState::open_files_mut(&mut self) -> &mut brush_core::openfiles::OpenFiles
+pub fn brush_core::ShellState::open_files_mut(&mut self) -> &mut brush_core::openfiles::async_file::AsyncOpenFiles

Performance Benchmark Report

Benchmark name Baseline (μs) Test/PR (μs) Delta (μs) Delta %
clone_shell_object 17.47 μs 17.45 μs -0.01 μs ⚪ Unchanged
eval_arithmetic 0.15 μs 0.15 μs 0.00 μs ⚪ Unchanged
expand_one_string 1.60 μs 1.61 μs 0.01 μs ⚪ Unchanged
for_loop 29.83 μs 30.18 μs 0.35 μs ⚪ Unchanged
full_peg_complex 58.46 μs 58.51 μs 0.05 μs ⚪ Unchanged
full_peg_for_loop 6.27 μs 6.43 μs 0.17 μs ⚪ Unchanged
full_peg_nested_expansions 16.32 μs 16.27 μs -0.05 μs ⚪ Unchanged
full_peg_pipeline 4.22 μs 4.29 μs 0.06 μs ⚪ Unchanged
full_peg_simple 1.84 μs 1.83 μs -0.01 μs ⚪ Unchanged
function_call 3.31 μs 3.30 μs -0.01 μs ⚪ Unchanged
instantiate_shell 53.97 μs 54.68 μs 0.71 μs ⚪ Unchanged
instantiate_shell_with_init_scripts 25487.19 μs 27100.35 μs 1613.16 μs 🟠 +6.33%
parse_peg_bash_completion 2098.52 μs 2111.51 μs 12.99 μs ⚪ Unchanged
parse_peg_complex 20.38 μs 20.48 μs 0.11 μs ⚪ Unchanged
parse_peg_for_loop 2.03 μs 2.00 μs -0.03 μs 🟢 -1.43%
parse_peg_pipeline 2.15 μs 2.18 μs 0.03 μs 🟠 +1.54%
parse_peg_simple 1.10 μs 1.16 μs 0.06 μs 🟠 +5.73%
run_echo_builtin_command 16.75 μs 55.77 μs 39.02 μs 🟠 +232.91%
tokenize_sample_script 3.41 μs 3.42 μs 0.01 μs ⚪ Unchanged

Code Coverage Report: Only Changed Files listed

Package Base Coverage New Coverage Difference
brush-builtins/src/alias.rs 🟢 79.17% 🟠 73.68% 🔴 -5.49%
brush-builtins/src/bind.rs 🟢 75.43% 🟠 73.94% 🔴 -1.49%
brush-builtins/src/caller.rs 🟢 95.65% 🟢 92.86% 🔴 -2.79%
brush-builtins/src/cd.rs 🟢 86.11% 🟢 77.55% 🔴 -8.56%
brush-builtins/src/command.rs 🟢 98.78% 🟢 96.74% 🔴 -2.04%
brush-builtins/src/complete.rs 🟢 83.38% 🟢 82.78% 🔴 -0.6%
brush-builtins/src/declare.rs 🟢 87.4% 🟢 86.77% 🔴 -0.63%
brush-builtins/src/dirs.rs 🟢 95.12% 🟢 91.67% 🔴 -3.45%
brush-builtins/src/echo.rs 🟢 89.74% 🟢 90.24% 🟢 0.5%
brush-builtins/src/enable.rs 🟢 80.49% 🟢 76.36% 🔴 -4.13%
brush-builtins/src/export.rs 🟠 73.17% 🟠 60.61% 🔴 -12.56%
brush-builtins/src/fc.rs 🟢 95.92% 🟢 94.34% 🔴 -1.58%
brush-builtins/src/getopts.rs 🟢 93.28% 🟢 93.25% 🔴 -0.03%
brush-builtins/src/hash.rs 🟢 92.45% 🟢 93.44% 🟢 0.99%
brush-builtins/src/help.rs 🟢 89.53% 🟢 88.54% 🔴 -0.99%
brush-builtins/src/history.rs 🟢 89.06% 🟢 87.39% 🔴 -1.67%
brush-builtins/src/kill.rs 🟠 54.76% 🔴 43.38% 🔴 -11.38%
brush-builtins/src/let_.rs 🟢 89.47% 🟠 70.83% 🔴 -18.64%
brush-builtins/src/mapfile.rs 🟢 88.35% 🟢 90.72% 🟢 2.37%
brush-builtins/src/printf.rs 🟢 93.2% 🟢 90.48% 🔴 -2.72%
brush-builtins/src/pwd.rs 🟢 100% 🟢 95.24% 🔴 -4.76%
brush-builtins/src/read.rs 🟢 91.42% 🟢 90.71% 🔴 -0.71%
brush-builtins/src/return_.rs 🟢 100% 🟢 94.74% 🔴 -5.26%
brush-builtins/src/set.rs 🟢 80.18% 🟢 80.67% 🟢 0.49%
brush-builtins/src/shopt.rs 🟠 73.42% 🟠 69% 🔴 -4.42%
brush-builtins/src/test.rs 🟢 94.12% 🟢 82.05% 🔴 -12.07%
brush-builtins/src/times.rs 🟢 89.47% 🟢 87.5% 🔴 -1.97%
brush-builtins/src/trap.rs 🟢 98.51% 🟢 97.3% 🔴 -1.21%
brush-builtins/src/type_.rs 🟢 83.48% 🟢 83.59% 🟢 0.11%
brush-builtins/src/ulimit.rs 🟠 66.67% 🟠 69.74% 🟢 3.07%
brush-builtins/src/unalias.rs 🟢 82.35% 🟢 83.33% 🟢 0.98%
brush-builtins/src/wait.rs 🟠 67.74% 🟠 56.76% 🔴 -10.98%
brush-core/src/commands.rs 🟢 91.65% 🟢 88.68% 🔴 -2.97%
brush-core/src/expansion.rs 🟢 95.51% 🟢 95.52% 🟢 0.01%
brush-core/src/interp.rs 🟢 93% 🟢 92.16% 🔴 -0.84%
brush-core/src/ioutils.rs 🔴 39.29% 🔴 0% 🔴 -39.29%
brush-core/src/openfiles.rs 🟠 63.64% 🔴 39.71% 🔴 -23.93%
brush-core/src/processes.rs 🟠 63.41% 🟠 63.64% 🟢 0.23%
brush-core/src/shell/execution.rs 🟢 100% 🟢 99.4% 🔴 -0.6%
brush-core/src/shell/history.rs 🟢 90.38% 🟢 91.49% 🟢 1.11%
brush-core/src/shell/io.rs 🟢 83.67% 🟢 79.63% 🔴 -4.04%
brush-core/src/shell/job_control.rs 🟢 87.5% 🟢 77.78% 🔴 -9.72%
brush-core/src/sys/unix/poll.rs 🟢 85.37% 🟢 82.93% 🔴 -2.44%
brush-shell/src/brushctl.rs 🔴 6.9% 🔴 4.2% 🔴 -2.7%
brush-shell/src/bundled.rs 🔴 18.75% 🔴 18.18% 🔴 -0.57%
Overall Coverage 🟢 75.23% 🟢 73.86% 🔴 -1.37%

Minimum allowed coverage is 70%, this run produced 73.86%

@lu-zero lu-zero force-pushed the async-openfile branch 2 times, most recently from 24aa365 to 5be0c65 Compare March 24, 2026 04:58
@reubeno reubeno added the state: needs review PR or issue author is waiting for a review label Mar 27, 2026
@lu-zero lu-zero force-pushed the async-openfile branch 4 times, most recently from c34119e to 85643c8 Compare April 26, 2026 18:48
Introduce AsyncOpenFile and related types to replace synchronous I/O
across the shell's file handling. This enables non-blocking I/O for
builtins, shell execution, and interactive features.

Key changes:
- Add AsyncOpenFile enum with tokio-based variants (File, Stdio, Pipe)
- Add AsyncStream trait for custom async I/O implementations
- Add AsyncOpenFiles wrapper for async file descriptor management
- Convert all builtins to use async write_all/flush patterns
- Fix signal race in process.wait() by polling for stopped children
  before entering tokio::select! loop (handles SIGCHLD arriving between
  SIGCONT and signal listener creation)
- Update ExecutionContext with async write helper methods
- Update examples to use async I/O patterns
lu-zero added 2 commits April 26, 2026 23:21
The async I/O refactor caused xtrace (set -x) output lines to appear in
a different order than bash on Linux. This happened because each
trace_command call cloned AsyncOpenFile::Stderr into a fresh
tokio::io::stderr() handle, and Linux's async I/O scheduling could
reorder small writes between handles.

Fix by using block_in_place + synchronous write on a dup'd FD, matching
the original behavior. This ensures trace output ordering is deterministic
while keeping the surrounding code async.
block_in_place and try_clone_to_owned are not available on WASM and
Windows. Fall back to async write_all + flush on non-unix platforms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state: needs review PR or issue author is waiting for a review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants