use tokio::io; use tokio::sync::mpsc; /// Exit status of a remote process #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ExitStatus { pub success: bool, pub code: Option, } impl ExitStatus { /// Produces a new exit status representing a killed process pub fn killed() -> Self { Self { success: false, code: None, } } } impl From> for ExitStatus where T: Into, E: Into, { fn from(res: Result) -> Self { match res { Ok(x) => x.into(), Err(x) => x.into(), } } } impl From for ExitStatus { fn from(err: io::Error) -> Self { Self { success: false, code: err.raw_os_error(), } } } impl From for ExitStatus { fn from(status: std::process::ExitStatus) -> Self { Self { success: status.success(), code: status.code(), } } } /// Creates a new channel for when the exit status will be ready pub fn channel() -> (WaitTx, WaitRx) { let (tx, rx) = mpsc::channel(1); (WaitTx::Pending(tx), WaitRx::Pending(rx)) } /// Represents a notifier for a specific waiting state #[derive(Debug)] pub enum WaitTx { /// Notification has been sent Done, /// Notification has not been sent Pending(mpsc::Sender), } impl WaitTx { /// Send exit status to receiving-side of wait pub async fn send(&mut self, status: S) -> io::Result<()> where S: Into, { let status = status.into(); match self { Self::Done => Err(io::Error::new( io::ErrorKind::BrokenPipe, "Notifier is closed", )), Self::Pending(tx) => { let res = tx.send(status).await; *self = Self::Done; match res { Ok(_) => Ok(()), Err(x) => Err(io::Error::new(io::ErrorKind::Other, x)), } } } } /// Mark wait as completed using killed status pub async fn kill(&mut self) -> io::Result<()> { self.send(ExitStatus::killed()).await } } /// Represents the state of waiting for an exit status #[derive(Debug)] pub enum WaitRx { /// Exit status is ready Ready(ExitStatus), /// If receiver for an exit status has been dropped without receiving the status Dropped, /// Exit status is not ready and has a "oneshot" to be invoked when available Pending(mpsc::Receiver), } impl WaitRx { /// Waits until the exit status is resolved; can be called repeatedly after being /// resolved to immediately return the exit status again pub async fn recv(&mut self) -> io::Result { match self { Self::Ready(status) => Ok(*status), Self::Dropped => Err(io::Error::new( io::ErrorKind::Other, "Internal resolver dropped", )), Self::Pending(rx) => match rx.recv().await { Some(status) => { *self = Self::Ready(status); Ok(status) } None => { *self = Self::Dropped; Err(io::Error::new( io::ErrorKind::Other, "Internal resolver dropped", )) } }, } } }