use serde_json::Value;
use std::{
collections::HashMap,
sync::atomic::{AtomicU64, Ordering},
};
use tokio::sync::oneshot;
use tracing::debug;
use crate::{
Transport, WireConn,
jsonrpc::{Id, Request, Response, RpcError},
};
pub struct Client {
conn: Box<dyn WireConn>,
next_id: AtomicU64,
}
impl Client {
pub async fn connect(transport: &dyn Transport, addr: &str) -> std::io::Result<Self> {
let conn = transport.connect(addr).await?;
Ok(Self::from_conn(conn))
}
pub fn from_conn(conn: Box<dyn WireConn>) -> Self {
Self {
conn,
next_id: AtomicU64::new(1),
}
}
pub async fn call(&mut self, method: &str, params: Value) -> Result<Value, RpcError> {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let req = Request::call(id, method, params);
let req_val = serde_json::to_value(&req)
.map_err(|e| RpcError::server(format!("encode request: {e}")))?;
self.conn
.write_msg(&req_val)
.await
.map_err(|e| RpcError::server(format!("write: {e}")))?;
loop {
let msg = self
.conn
.read_msg()
.await
.map_err(|e| RpcError::server(format!("read: {e}")))?
.ok_or_else(|| RpcError::server("connection closed before response"))?;
let resp: Response = serde_json::from_value(msg)
.map_err(|e| RpcError::server(format!("decode response: {e}")))?;
if resp.id != Id::Num(id) {
debug!(?resp.id, "ignoring mismatched response id");
continue;
}
if let Some(err) = resp.error {
return Err(err);
}
return Ok(resp.result.unwrap_or(Value::Null));
}
}
pub async fn notify(&mut self, method: &str, params: Value) -> Result<(), RpcError> {
let req = Request::notify(method, params);
let req_val = serde_json::to_value(&req)
.map_err(|e| RpcError::server(format!("encode notification: {e}")))?;
self.conn
.write_msg(&req_val)
.await
.map_err(|e| RpcError::server(format!("write: {e}")))?;
Ok(())
}
}
struct CallMsg {
method: String,
params: Value,
reply: oneshot::Sender<Result<Value, RpcError>>,
}
pub struct ClientPool {
senders: Vec<tokio::sync::mpsc::Sender<CallMsg>>,
next: AtomicU64,
}
impl ClientPool {
pub async fn new(transport: &dyn Transport, addr: &str, size: usize) -> std::io::Result<Self> {
let mut senders = Vec::with_capacity(size);
let mut next_id = 1u64;
for _ in 0..size {
let conn = transport.connect(addr).await?;
let (tx, rx) = tokio::sync::mpsc::channel::<CallMsg>(128);
senders.push(tx);
tokio::spawn(conn_task(conn, rx, next_id));
next_id += 1_000_000; }
Ok(Self {
senders,
next: AtomicU64::new(0),
})
}
pub async fn call(&self, method: &str, params: Value) -> Result<Value, RpcError> {
let len = self.senders.len();
let idx = self.next.fetch_add(1, Ordering::Relaxed) as usize % len;
let (reply_tx, reply_rx) = oneshot::channel();
self.senders[idx]
.send(CallMsg {
method: method.to_string(),
params,
reply: reply_tx,
})
.await
.map_err(|_| RpcError::server("connection task closed"))?;
reply_rx
.await
.map_err(|_| RpcError::server("connection task dropped reply"))?
}
}
async fn conn_task(
mut conn: Box<dyn WireConn>,
mut rx: tokio::sync::mpsc::Receiver<CallMsg>,
id_base: u64,
) {
let mut next_id = id_base;
let mut pending: HashMap<u64, oneshot::Sender<Result<Value, RpcError>>> = HashMap::new();
loop {
tokio::select! {
maybe_msg = rx.recv() => {
let Some(msg) = maybe_msg else { return; };
let id = next_id;
next_id += 1;
let req = Request::call(id, &msg.method, msg.params);
let req_val = match serde_json::to_value(&req) {
Ok(v) => v,
Err(e) => {
let _ = msg.reply.send(Err(RpcError::server(format!("encode: {e}"))));
continue;
}
};
if let Err(e) = conn.write_msg(&req_val).await {
let _ = msg.reply.send(Err(RpcError::server(format!("write: {e}"))));
return; }
pending.insert(id, msg.reply);
}
result = conn.read_msg() => {
match result {
Ok(Some(value)) => {
let resp: Response = match serde_json::from_value(value) {
Ok(r) => r,
Err(e) => {
debug!(error = %e, "malformed response frame");
continue;
}
};
if let Id::Num(id) = resp.id {
if let Some(tx) = pending.remove(&id) {
let r = if let Some(err) = resp.error {
Err(err)
} else {
Ok(resp.result.unwrap_or(Value::Null))
};
let _ = tx.send(r);
}
}
}
Ok(None) => {
debug!("connection closed by peer");
return;
}
Err(e) => {
debug!(error = %e, "read error; closing connection");
return;
}
}
}
}
}
}