use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use anyhow::{Result, anyhow};
use serde_json::Value;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::sync::{Mutex, mpsc, oneshot};
use super::protocol::{self, ClientMessage, RpcError};
static NEXT_REQUEST_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Default)]
struct Pending {
waiting: Mutex<HashMap<u64, oneshot::Sender<Result<Value, RpcError>>>>,
}
struct TransportInner {
write_tx: mpsc::UnboundedSender<Value>,
pending: Pending,
}
#[derive(Clone)]
pub struct TransportHandle {
inner: Arc<TransportInner>,
}
pub struct Transport {
inbound_rx: mpsc::UnboundedReceiver<ClientMessage>,
inner: Arc<TransportInner>,
}
impl Transport {
pub fn spawn() -> Self {
let (inbound_tx, inbound_rx) = mpsc::unbounded_channel::<ClientMessage>();
let (write_tx, write_rx) = mpsc::unbounded_channel::<Value>();
let inner = Arc::new(TransportInner {
write_tx: write_tx.clone(),
pending: Pending::default(),
});
tokio::spawn(reader_task(inbound_tx, write_tx, inner.clone()));
tokio::spawn(writer_task(write_rx));
Self { inbound_rx, inner }
}
pub fn handle(&self) -> TransportHandle {
TransportHandle {
inner: self.inner.clone(),
}
}
pub async fn next(&mut self) -> Option<ClientMessage> {
self.inbound_rx.recv().await
}
}
impl TransportHandle {
pub fn send(&self, frame: Value) {
if self.inner.write_tx.send(frame).is_err() {
tracing::warn!("acp transport: writer gone, dropping frame");
}
}
pub fn respond(&self, id: Value, result: Value) {
self.send(protocol::result_response(id, result));
}
pub fn respond_error(&self, id: Value, code: i64, message: impl Into<String>) {
self.send(protocol::error_response(id, code, message));
}
pub fn notify(&self, method: &str, params: Value) {
self.send(protocol::notification(method, params));
}
pub async fn call(&self, method: &str, params: Value) -> Result<Value> {
let id = NEXT_REQUEST_ID.fetch_add(1, Ordering::Relaxed);
let (tx, rx) = oneshot::channel();
self.inner.pending.waiting.lock().await.insert(id, tx);
self.send(protocol::request_frame(id, method, params));
match rx.await {
Ok(Ok(value)) => Ok(value),
Ok(Err(err)) => Err(anyhow!("client error {}: {}", err.code, err.message)),
Err(_) => Err(anyhow!("connection closed awaiting {method} response")),
}
}
}
async fn reader_task(
inbound_tx: mpsc::UnboundedSender<ClientMessage>,
write_tx: mpsc::UnboundedSender<Value>,
inner: Arc<TransportInner>,
) {
let mut reader = BufReader::new(tokio::io::stdin());
loop {
match read_capped_line(&mut reader, MAX_LINE_BYTES).await {
LineRead::Line(bytes) => {
let line = String::from_utf8_lossy(&bytes);
if line.trim().is_empty() {
continue;
}
match protocol::parse_line(&line) {
Ok(ClientMessage::Response { id, result }) => {
if let Some(tx) = inner.pending.waiting.lock().await.remove(&id) {
let _ = tx.send(result);
} else {
tracing::debug!("acp transport: response for unknown id {id}");
}
}
Ok(msg) => {
if inbound_tx.send(msg).is_err() {
return; }
}
Err((id, err)) => {
let frame = protocol::error_response(id, err.code, err.message);
if write_tx.send(frame).is_err() {
return;
}
}
}
}
LineRead::Oversized => {
let frame = protocol::error_response(
serde_json::Value::Null,
protocol::PARSE_ERROR,
format!("inbound line exceeds {MAX_LINE_BYTES} bytes"),
);
if write_tx.send(frame).is_err() {
return;
}
}
LineRead::Eof => {
fail_pending(&inner, "client closed stdin").await;
return;
}
LineRead::Failed(e) => {
tracing::warn!("acp transport: stdin read failed: {e}");
fail_pending(&inner, "stdin read failed").await;
return;
}
}
}
}
const MAX_LINE_BYTES: usize = 64 * 1024 * 1024;
#[derive(Debug)]
pub(crate) enum LineRead {
Line(Vec<u8>),
Eof,
Failed(std::io::Error),
Oversized,
}
pub(crate) async fn read_capped_line<R: tokio::io::AsyncRead + Unpin>(
reader: &mut BufReader<R>,
cap: usize,
) -> LineRead {
let mut line: Vec<u8> = Vec::new();
let mut overflowing = false;
loop {
let chunk = match reader.fill_buf().await {
Ok(c) => c,
Err(e) => return LineRead::Failed(e),
};
if chunk.is_empty() {
return if overflowing {
LineRead::Oversized
} else if line.is_empty() {
LineRead::Eof
} else {
LineRead::Line(line)
};
}
match chunk.iter().position(|&b| b == b'\n') {
Some(pos) => {
if !overflowing {
line.extend_from_slice(&chunk[..pos]);
}
reader.consume(pos + 1);
return if overflowing || line.len() > cap {
LineRead::Oversized
} else {
LineRead::Line(line)
};
}
None => {
if !overflowing {
line.extend_from_slice(chunk);
if line.len() > cap {
line.clear();
overflowing = true;
}
}
let n = chunk.len();
reader.consume(n);
}
}
}
}
async fn fail_pending(inner: &TransportInner, reason: &str) {
let mut waiting = inner.pending.waiting.lock().await;
for (_, tx) in waiting.drain() {
let _ = tx.send(Err(RpcError {
code: -32000,
message: reason.to_string(),
}));
}
}
async fn writer_task(mut rx: mpsc::UnboundedReceiver<Value>) {
let mut stdout = tokio::io::stdout();
while let Some(frame) = rx.recv().await {
let mut line = match serde_json::to_string(&frame) {
Ok(s) => s,
Err(e) => {
tracing::warn!("acp transport: frame failed to serialize: {e}");
continue;
}
};
line.push('\n');
if stdout.write_all(line.as_bytes()).await.is_err() {
return;
}
if stdout.flush().await.is_err() {
return;
}
}
}