Fix a deadlock in the log-stream handler that caused all logs requests to hang: Connection used a single Mutex<JsonFramed> for both reads and writes, so the serve loop holding the read lock blocked the spawned notification task from writing. Split Connection into separate reader and writer mutexes. Also fix a response/notification ordering race: the log task now waits for an explicit ready signal sent by serve after writing the LOGS response, ensuring notifications never arrive at the client before their initiating response.
128 lines
3.9 KiB
Rust
128 lines
3.9 KiB
Rust
use crate::envelope::{Incoming, Notification, Request};
|
|
use crate::framing::JsonFramed;
|
|
use serde::Serialize;
|
|
use serde::de::DeserializeOwned;
|
|
use std::path::Path;
|
|
use thiserror::Error;
|
|
use tokio::net::UnixStream;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ClientError {
|
|
#[error("io: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
#[error("rpc error {code}: {message}")]
|
|
Rpc { code: i32, message: String },
|
|
#[error("unexpected message kind from daemon")]
|
|
Unexpected,
|
|
#[error("daemon unreachable: {0}")]
|
|
Unreachable(std::io::Error),
|
|
#[error("serialization: {0}")]
|
|
Serde(#[from] serde_json::Error),
|
|
}
|
|
|
|
pub struct Client {
|
|
framed: JsonFramed,
|
|
next_id: u64,
|
|
}
|
|
|
|
impl Client {
|
|
pub async fn connect(socket_path: &Path) -> Result<Self, ClientError> {
|
|
let stream = UnixStream::connect(socket_path)
|
|
.await
|
|
.map_err(ClientError::Unreachable)?;
|
|
Ok(Self {
|
|
framed: JsonFramed::new(stream),
|
|
next_id: 1,
|
|
})
|
|
}
|
|
|
|
pub async fn call<P: Serialize, R: DeserializeOwned>(
|
|
&mut self,
|
|
method: &str,
|
|
params: &P,
|
|
) -> Result<R, ClientError> {
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
|
|
let params_val = serde_json::to_value(params)?;
|
|
let req = crate::envelope::request(id, method, Some(params_val));
|
|
self.framed.write(&req).await?;
|
|
|
|
loop {
|
|
let msg: Option<Incoming> = self.framed.read().await?;
|
|
let Some(msg) = msg else {
|
|
return Err(ClientError::Unreachable(std::io::Error::from(
|
|
std::io::ErrorKind::UnexpectedEof,
|
|
)));
|
|
};
|
|
|
|
match msg {
|
|
Incoming::Response(r) => {
|
|
if r.id != serde_json::json!(id) {
|
|
return Err(ClientError::Unexpected);
|
|
}
|
|
if let Some(err) = r.error {
|
|
return Err(ClientError::Rpc {
|
|
code: err.code,
|
|
message: err.message,
|
|
});
|
|
}
|
|
let result = r.result.unwrap_or(serde_json::Value::Null);
|
|
return Ok(serde_json::from_value(result)?);
|
|
}
|
|
Incoming::Notification(_) => continue,
|
|
Incoming::Request(_) => return Err(ClientError::Unexpected),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn call_no_params<R: DeserializeOwned>(
|
|
&mut self,
|
|
method: &str,
|
|
) -> Result<R, ClientError> {
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
|
|
let req = Request {
|
|
jsonrpc: "2.0".into(),
|
|
id: serde_json::json!(id),
|
|
method: method.into(),
|
|
params: None,
|
|
};
|
|
self.framed.write(&req).await?;
|
|
|
|
let msg: Option<Incoming> = self.framed.read().await?;
|
|
let Some(Incoming::Response(r)) = msg else {
|
|
return Err(ClientError::Unexpected);
|
|
};
|
|
|
|
if let Some(err) = r.error {
|
|
return Err(ClientError::Rpc {
|
|
code: err.code,
|
|
message: err.message,
|
|
});
|
|
}
|
|
|
|
Ok(serde_json::from_value(
|
|
r.result.unwrap_or(serde_json::Value::Null),
|
|
)?)
|
|
}
|
|
|
|
pub async fn read_notification(&mut self) -> Result<Option<Notification>, ClientError> {
|
|
loop {
|
|
let msg: Option<Incoming> = self.framed.read().await?;
|
|
match msg {
|
|
None => return Ok(None),
|
|
Some(Incoming::Notification(n)) => return Ok(Some(n)),
|
|
Some(Incoming::Response(_)) => continue,
|
|
Some(Incoming::Request(_)) => return Err(ClientError::Unexpected),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn send_notification(&mut self, n: &Notification) -> Result<(), ClientError> {
|
|
self.framed.write(n).await?;
|
|
Ok(())
|
|
}
|
|
}
|