diff --git a/radicle-cli/src/commands/node/events.rs b/radicle-cli/src/commands/node/events.rs index 7e822c64..a90ef920 100644 --- a/radicle-cli/src/commands/node/events.rs +++ b/radicle-cli/src/commands/node/events.rs @@ -1,10 +1,13 @@ use std::time; -use radicle::node::Handle; +use radicle::node::{Event, Handle}; -pub fn run(node: impl Handle, count: usize, timeout: time::Duration) -> anyhow::Result<()> { +pub fn run(node: H, count: usize, timeout: time::Duration) -> anyhow::Result<()> +where + H: Handle::Error>>, +{ let events = node.subscribe(timeout)?; - for (i, event) in events.enumerate() { + for (i, event) in events.into_iter().enumerate() { let event = event?; let obj = serde_json::to_string(&event)?; diff --git a/radicle-node/src/control.rs b/radicle-node/src/control.rs index e491e519..23d087fc 100644 --- a/radicle-node/src/control.rs +++ b/radicle-node/src/control.rs @@ -30,12 +30,12 @@ pub enum Error { } /// Listen for commands on the control socket, and process them. -pub fn listen + 'static>( - listener: UnixListener, - handle: H, -) -> Result<(), Error> +pub fn listen(listener: UnixListener, handle: H) -> Result<(), Error> where + H: Handle + 'static, H::Sessions: serde::Serialize, + CommandResult: From, + E: serde::Serialize, { log::debug!(target: "control", "Control thread listening on socket.."); let nid = handle.nid()?; @@ -74,12 +74,12 @@ enum CommandError { Io(#[from] io::Error), } -fn command + 'static>( - stream: &UnixStream, - mut handle: H, -) -> Result<(), CommandError> +fn command(stream: &UnixStream, mut handle: H) -> Result<(), CommandError> where + H: Handle + 'static, H::Sessions: serde::Serialize, + CommandResult: From, + E: serde::Serialize, { let mut reader = BufReader::new(stream); let mut writer = LineWriter::new(stream); @@ -185,8 +185,7 @@ where Command::Subscribe => match handle.subscribe(MAX_TIMEOUT) { Ok(events) => { for e in events { - let event = e?; - CommandResult::Okay(event).to_writer(&mut writer)?; + CommandResult::from(e).to_writer(&mut writer)?; } } Err(e) => return Err(CommandError::Runtime(e)), diff --git a/radicle-node/src/runtime/handle.rs b/radicle-node/src/runtime/handle.rs index 16054129..ac329979 100644 --- a/radicle-node/src/runtime/handle.rs +++ b/radicle-node/src/runtime/handle.rs @@ -124,6 +124,8 @@ impl Handle { impl radicle::node::Handle for Handle { type Sessions = Vec; + type Events = Events; + type Event = Event; type Error = Error; fn nid(&self) -> Result { @@ -263,11 +265,8 @@ impl radicle::node::Handle for Handle { receiver.recv().map_err(Error::from) } - fn subscribe( - &self, - _timeout: time::Duration, - ) -> Result>>, Error> { - Ok(Box::new(self.events().into_iter().map(Ok))) + fn subscribe(&self, _timeout: time::Duration) -> Result { + Ok(self.events()) } fn sessions(&self) -> Result { diff --git a/radicle-node/src/test/handle.rs b/radicle-node/src/test/handle.rs index 87de45ee..52efaa59 100644 --- a/radicle-node/src/test/handle.rs +++ b/radicle-node/src/test/handle.rs @@ -22,6 +22,8 @@ pub struct Handle { impl radicle::node::Handle for Handle { type Error = HandleError; type Sessions = Vec; + type Events = Vec; + type Event = Result; fn nid(&self) -> Result { Ok(NodeId::from_str("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap()) @@ -81,11 +83,8 @@ impl radicle::node::Handle for Handle { Ok(self.following.lock().unwrap().insert(id)) } - fn subscribe( - &self, - _timeout: time::Duration, - ) -> Result>>, Self::Error> { - Ok(Box::new(std::iter::empty())) + fn subscribe(&self, _timeout: time::Duration) -> Result { + Ok(vec![]) } fn unfollow(&mut self, id: NodeId) -> Result { diff --git a/radicle/src/node.rs b/radicle/src/node.rs index 9874b27b..97364267 100644 --- a/radicle/src/node.rs +++ b/radicle/src/node.rs @@ -15,6 +15,7 @@ pub mod timestamp; use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; use std::io::{BufRead, BufReader}; +use std::marker::PhantomData; use std::ops::{ControlFlow, Deref}; use std::os::unix::net::UnixStream; use std::path::{Path, PathBuf}; @@ -50,6 +51,9 @@ pub const DEFAULT_SOCKET_NAME: &str = "control.sock"; pub const DEFAULT_PORT: u16 = 8776; /// Default timeout when waiting for the node to respond with data. pub const DEFAULT_TIMEOUT: time::Duration = time::Duration::from_secs(30); +/// Default timeout when waiting for an event to be received on the +/// [`Handle::subscribe`] channel. +pub const DEFAULT_SUBSCRIBE_TIMEOUT: time::Duration = time::Duration::from_secs(5); /// Maximum length in bytes of a node alias. pub const MAX_ALIAS_LENGTH: usize = 32; /// Penalty threshold at which point we avoid connecting to this node. @@ -338,6 +342,26 @@ pub enum CommandResult { }, } +impl From> for CommandResult +where + E: std::error::Error, +{ + fn from(result: Result) -> Self { + match result { + Ok(t) => Self::Okay(t), + Err(e) => Self::Error { + reason: e.to_string(), + }, + } + } +} + +impl From for CommandResult { + fn from(event: Event) -> Self { + Self::Okay(event) + } +} + /// A success response. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Success { @@ -856,6 +880,8 @@ pub enum ConnectResult { pub trait Handle: Clone + Sync + Send { /// The peer sessions type. type Sessions; + type Events: IntoIterator; + type Event; /// The error returned by all methods. type Error: std::error::Error + Send + Sync + 'static; @@ -905,10 +931,53 @@ pub trait Handle: Clone + Sync + Send { /// Query the peer session state. fn sessions(&self) -> Result; /// Subscribe to node events. - fn subscribe( - &self, - timeout: time::Duration, - ) -> Result>>, Self::Error>; + fn subscribe(&self, timeout: time::Duration) -> Result; +} + +/// Iterator of results `T` when passing a [`Command`] to [`Node::call`]. +/// +/// The iterator blocks for a `timeout` duration, returning [`Error::TimedOut`] +/// if the duration is reached. +pub struct LineIter { + stream: BufReader, + timeout: time::Duration, + witness: PhantomData, +} + +impl Iterator for LineIter { + type Item = Result; + + fn next(&mut self) -> Option { + let mut l = String::new(); + + self.stream + .get_ref() + .set_read_timeout(Some(self.timeout)) + .ok(); + + match self.stream.read_line(&mut l) { + Ok(0) => None, + Ok(_) => { + let result: CommandResult = match json::from_str(&l) { + Err(e) => { + return Some(Err(Error::InvalidJson { + response: l.clone(), + error: e, + })) + } + Ok(result) => result, + }; + match result { + CommandResult::Okay(result) => Some(Ok(result)), + CommandResult::Error { reason } => Some(Err(Error::Command { reason })), + } + } + Err(e) => match e.kind() { + io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => Some(Err(Error::TimedOut)), + _ => Some(Err(Error::Io(e))), + }, + } + } } /// Public node & device identifier. @@ -929,33 +998,19 @@ impl Node { } /// Call a command on the node. - pub fn call( + pub fn call( &self, cmd: Command, timeout: time::Duration, - ) -> Result>, Error> { + ) -> Result, Error> { let stream = UnixStream::connect(&self.socket) .map_err(|e| Error::Connect(self.socket.clone(), e.kind()))?; cmd.to_writer(&stream)?; - - stream.set_read_timeout(Some(timeout))?; - - Ok(BufReader::new(stream).lines().map(move |l| { - let l = l.map_err(|e| match e.kind() { - io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => Error::TimedOut, - _ => Error::Io(e), - })?; - - let result: CommandResult = json::from_str(&l).map_err(|e| Error::InvalidJson { - response: l.clone(), - error: e, - })?; - - match result { - CommandResult::Okay(result) => Ok(result), - CommandResult::Error { reason } => Err(Error::Command { reason }), - } - })) + Ok(LineIter { + stream: BufReader::new(stream), + timeout, + witness: PhantomData, + }) } /// Announce refs of the given `rid` to the given seeds. @@ -1029,6 +1084,8 @@ impl Node { // attempt to return iterators instead of allocating vecs. impl Handle for Node { type Sessions = Vec; + type Events = LineIter; + type Event = Result; type Error = Error; fn nid(&self) -> Result { @@ -1049,6 +1106,7 @@ impl Handle for Node { let Ok(mut lines) = self.call::(Command::Status, DEFAULT_TIMEOUT) else { return false; }; + let Some(Ok(_)) = lines.next() else { return false; }; @@ -1122,29 +1180,29 @@ impl Handle for Node { } fn follow(&mut self, nid: NodeId, alias: Option) -> Result { - let mut line = self.call::(Command::Follow { nid, alias }, DEFAULT_TIMEOUT)?; - let response = line.next().ok_or(Error::EmptyResponse)??; + let mut lines = self.call::(Command::Follow { nid, alias }, DEFAULT_TIMEOUT)?; + let response = lines.next().ok_or(Error::EmptyResponse)??; Ok(response.updated) } fn seed(&mut self, rid: RepoId, scope: policy::Scope) -> Result { - let mut line = self.call::(Command::Seed { rid, scope }, DEFAULT_TIMEOUT)?; - let response = line.next().ok_or(Error::EmptyResponse)??; + let mut lines = self.call::(Command::Seed { rid, scope }, DEFAULT_TIMEOUT)?; + let response = lines.next().ok_or(Error::EmptyResponse)??; Ok(response.updated) } fn unfollow(&mut self, nid: NodeId) -> Result { - let mut line = self.call::(Command::Unfollow { nid }, DEFAULT_TIMEOUT)?; - let response = line.next().ok_or(Error::EmptyResponse)??; + let mut lines = self.call::(Command::Unfollow { nid }, DEFAULT_TIMEOUT)?; + let response = lines.next().ok_or(Error::EmptyResponse)??; Ok(response.updated) } fn unseed(&mut self, rid: RepoId) -> Result { - let mut line = self.call::(Command::Unseed { rid }, DEFAULT_TIMEOUT)?; - let response = line.next().ok_or(Error::EmptyResponse {})??; + let mut lines = self.call::(Command::Unseed { rid }, DEFAULT_TIMEOUT)?; + let response = lines.next().ok_or(Error::EmptyResponse)??; Ok(response.updated) } @@ -1166,26 +1224,21 @@ impl Handle for Node { } fn update_inventory(&mut self, rid: RepoId) -> Result { - let mut line = self.call::(Command::UpdateInventory { rid }, DEFAULT_TIMEOUT)?; - let response = line.next().ok_or(Error::EmptyResponse {})??; + let mut lines = self.call::(Command::UpdateInventory { rid }, DEFAULT_TIMEOUT)?; + let response = lines.next().ok_or(Error::EmptyResponse)??; Ok(response.updated) } - fn subscribe( - &self, - timeout: time::Duration, - ) -> Result>>, Error> { - let events = self.call(Command::Subscribe, timeout)?; - - Ok(Box::new(events)) + fn subscribe(&self, timeout: time::Duration) -> Result, Error> { + self.call(Command::Subscribe, timeout) } fn sessions(&self) -> Result { let sessions = self .call::>(Command::Sessions, DEFAULT_TIMEOUT)? .next() - .ok_or(Error::EmptyResponse {})??; + .ok_or(Error::EmptyResponse)??; Ok(sessions) }