use std::collections::HashMap;
use std::io;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::Duration;
use serde_json::Value;
use tokio::io::{AsyncBufRead, AsyncWrite};
use tokio::sync::{Mutex, oneshot};
use tokio::task::JoinHandle;
use tokio::time::timeout;
use crate::jsonrpc_framing::{decode_frame, encode_frame};
const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) trait RpcErr: From<io::Error> + From<serde_json::Error> + Send + 'static {
fn connection_closed() -> Self;
fn timeout(duration: Duration) -> Self;
}
pub(crate) enum Incoming<E> {
Response { id: u64, result: Result<Value, E> },
Notify { key: String, body: Value },
ReverseRequest { ack: Value },
Ignore,
}
pub(crate) trait Protocol: 'static {
type Error: RpcErr;
fn name() -> &'static str;
fn build_request(id: u64, method: &str, params: Value) -> Value;
fn build_notification(id: u64, method: &str, params: Value) -> Value;
fn classify(msg: &Value) -> Incoming<Self::Error>;
}
type Pending<E> = HashMap<u64, oneshot::Sender<Result<Value, E>>>;
type Handler = Arc<dyn Fn(Value) + Send + Sync>;
pub(crate) struct Inner<E> {
pub(crate) next_id: AtomicU64,
pub(crate) pending: Mutex<Pending<E>>,
pub(crate) handlers: Mutex<HashMap<String, Handler>>,
pub(crate) writer: Mutex<Box<dyn AsyncWrite + Send + Unpin>>,
pub(crate) closed: AtomicBool,
}
pub(crate) fn new<P, R, W>(
reader: R,
writer: W,
) -> (Arc<Inner<P::Error>>, JoinHandle<io::Result<()>>)
where
P: Protocol,
R: AsyncBufRead + Send + Unpin + 'static,
W: AsyncWrite + Send + Unpin + 'static,
{
let inner = Arc::new(Inner::<P::Error> {
next_id: AtomicU64::new(1),
pending: Mutex::new(HashMap::new()),
handlers: Mutex::new(HashMap::new()),
writer: Mutex::new(Box::new(writer)),
closed: AtomicBool::new(false),
});
let task = tokio::spawn(read_loop::<P, R>(inner.clone(), reader));
(inner, task)
}
async fn fail_transport<E: RpcErr>(inner: &Inner<E>) {
inner.closed.store(true, Ordering::SeqCst);
let mut pending = inner.pending.lock().await;
for (_, sender) in pending.drain() {
let _ = sender.send(Err(E::connection_closed()));
}
}
pub(crate) async fn request<P, Params, R>(
inner: &Inner<P::Error>,
method: &str,
params: Params,
request_timeout: Duration,
) -> Result<R, P::Error>
where
P: Protocol,
Params: serde::Serialize,
R: serde::de::DeserializeOwned,
{
if inner.closed.load(Ordering::SeqCst) {
return Err(P::Error::connection_closed());
}
let id = inner.next_id.fetch_add(1, Ordering::SeqCst);
let (tx, rx) = oneshot::channel();
inner.pending.lock().await.insert(id, tx);
let body = P::build_request(id, method, serde_json::to_value(params)?);
let bytes = serde_json::to_vec(&body)?;
let send_result = timeout(WRITE_TIMEOUT, async {
let mut writer = inner.writer.lock().await;
encode_frame(&mut *writer, &bytes).await
})
.await;
match send_result {
Ok(Ok(())) => {}
Ok(Err(e)) => {
fail_transport(inner).await;
return Err(P::Error::from(e));
}
Err(_) => {
fail_transport(inner).await;
return Err(P::Error::timeout(WRITE_TIMEOUT));
}
}
let value = match timeout(request_timeout, rx).await {
Ok(Ok(result)) => result?,
Ok(Err(_)) => {
inner.pending.lock().await.remove(&id);
return Err(P::Error::connection_closed());
}
Err(_) => {
inner.pending.lock().await.remove(&id);
return Err(P::Error::timeout(request_timeout));
}
};
Ok(serde_json::from_value(value)?)
}
pub(crate) async fn notify<P, Params>(
inner: &Inner<P::Error>,
method: &str,
params: Params,
) -> Result<(), P::Error>
where
P: Protocol,
Params: serde::Serialize,
{
if inner.closed.load(Ordering::SeqCst) {
return Err(P::Error::connection_closed());
}
let id = inner.next_id.fetch_add(1, Ordering::SeqCst);
let body = P::build_notification(id, method, serde_json::to_value(params)?);
let bytes = serde_json::to_vec(&body)?;
match timeout(WRITE_TIMEOUT, async {
let mut writer = inner.writer.lock().await;
encode_frame(&mut *writer, &bytes).await
})
.await
{
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => {
fail_transport(inner).await;
Err(P::Error::from(e))
}
Err(_) => {
fail_transport(inner).await;
Err(P::Error::timeout(WRITE_TIMEOUT))
}
}
}
pub(crate) async fn register_notification<E>(inner: &Inner<E>, method: &str, handler: Handler) {
inner
.handlers
.lock()
.await
.insert(method.to_string(), handler);
}
pub(crate) async fn read_loop<P, R>(inner: Arc<Inner<P::Error>>, mut reader: R) -> io::Result<()>
where
P: Protocol,
R: AsyncBufRead + Send + Unpin,
{
let name = P::name();
let mut exit_err: Option<io::Error> = None;
loop {
let frame = match decode_frame(&mut reader).await {
Ok(b) => b,
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
break;
}
Err(e) => {
tracing::warn!("{name}: read loop aborting on decode error: {e}");
exit_err = Some(e);
break;
}
};
let msg: Value = match serde_json::from_slice(&frame) {
Ok(v) => v,
Err(e) => {
tracing::warn!("{name}: skipping non-JSON frame: {e}");
continue;
}
};
dispatch::<P>(&inner, msg).await;
}
inner.closed.store(true, Ordering::SeqCst);
let mut pending = inner.pending.lock().await;
for (_, sender) in pending.drain() {
let _ = sender.send(Err(P::Error::connection_closed()));
}
drop(pending);
match exit_err {
Some(e) => Err(e),
None => Ok(()),
}
}
async fn dispatch<P: Protocol>(inner: &Arc<Inner<P::Error>>, msg: Value) {
match P::classify(&msg) {
Incoming::Response { id, result } => {
let sender = inner.pending.lock().await.remove(&id);
if let Some(sender) = sender {
let _ = sender.send(result);
}
}
Incoming::Notify { key, body } => {
let handler = inner.handlers.lock().await.get(&key).cloned();
if let Some(handler) = handler {
handler(body);
}
}
Incoming::ReverseRequest { ack } => {
if let Ok(bytes) = serde_json::to_vec(&ack) {
match timeout(WRITE_TIMEOUT, async {
let mut writer = inner.writer.lock().await;
encode_frame(&mut *writer, &bytes).await
})
.await
{
Ok(Ok(())) => {}
Ok(Err(e)) => {
tracing::warn!("{}: reverse-request ack write failed: {e}", P::name());
}
Err(_) => {
tracing::warn!(
"{}: reverse-request ack write timed out after {:?}; \
dropping ack to unblock the read loop",
P::name(),
WRITE_TIMEOUT,
);
}
}
}
}
Incoming::Ignore => {
tracing::warn!("{}: ignoring frame", P::name());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, BufReader, ReadBuf};
struct BlockingWriter;
impl AsyncWrite for BlockingWriter {
fn poll_write(
self: Pin<&mut Self>,
_: &mut Context<'_>,
_: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Pending
}
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Pending
}
fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
#[derive(Debug, PartialEq)]
enum TestErr {
Closed,
Timeout,
Io,
Serde,
}
impl From<io::Error> for TestErr {
fn from(_: io::Error) -> Self {
TestErr::Io
}
}
impl From<serde_json::Error> for TestErr {
fn from(_: serde_json::Error) -> Self {
TestErr::Serde
}
}
impl RpcErr for TestErr {
fn connection_closed() -> Self {
TestErr::Closed
}
fn timeout(_d: Duration) -> Self {
TestErr::Timeout
}
}
struct TestProto;
impl Protocol for TestProto {
type Error = TestErr;
fn name() -> &'static str {
"test"
}
fn build_request(id: u64, method: &str, params: Value) -> Value {
serde_json::json!({"id": id, "method": method, "params": params})
}
fn build_notification(id: u64, method: &str, params: Value) -> Value {
serde_json::json!({"id": id, "method": method, "params": params})
}
fn classify(_msg: &Value) -> Incoming<TestErr> {
Incoming::ReverseRequest {
ack: serde_json::json!({"id": 1, "result": null}),
}
}
}
struct FailingWriter;
impl AsyncWrite for FailingWriter {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(Err(io::Error::new(io::ErrorKind::BrokenPipe, "boom")))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
struct ParkedReader;
impl AsyncRead for ParkedReader {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Poll::Pending
}
}
#[tokio::test(start_paused = true)]
async fn reverse_request_ack_is_bounded_by_write_timeout() {
let inner = Arc::new(Inner::<TestErr> {
next_id: AtomicU64::new(1),
pending: Mutex::new(HashMap::new()),
handlers: Mutex::new(HashMap::new()),
writer: Mutex::new(Box::new(BlockingWriter)),
closed: AtomicBool::new(false),
});
let result = tokio::time::timeout(
WRITE_TIMEOUT + Duration::from_secs(30),
dispatch::<TestProto>(&inner, Value::Null),
)
.await;
assert!(
result.is_ok(),
"reverse-request ack must not block the read loop indefinitely",
);
}
#[tokio::test]
async fn write_failure_closes_transport() {
let (inner, task) = new::<TestProto, _, _>(BufReader::new(ParkedReader), FailingWriter);
let r = request::<TestProto, _, Value>(
&inner,
"m",
serde_json::json!({}),
Duration::from_secs(5),
)
.await;
assert!(r.is_err());
assert!(
inner.closed.load(Ordering::SeqCst),
"write failure must close the transport"
);
let r2 = request::<TestProto, _, Value>(
&inner,
"m",
serde_json::json!({}),
Duration::from_secs(5),
)
.await;
assert!(
matches!(r2, Err(TestErr::Closed)),
"second request must fail fast as closed"
);
task.abort();
}
}