use futures::FutureExt as _;
use futures::StreamExt as _;
use futures::channel::mpsc;
use futures::stream;
use futures_concurrency::stream::StreamExt as _;
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use uuid::Uuid;
use crate::Dispatch;
use crate::UntypedMessage;
use crate::jsonrpc::ConnectionTo;
use crate::jsonrpc::HandleConnectionClose;
use crate::jsonrpc::HandleDispatchFrom;
use crate::jsonrpc::HandlerErrorTarget;
use crate::jsonrpc::OutgoingMessage;
use crate::jsonrpc::PendingReplies;
use crate::jsonrpc::PendingReply;
use crate::jsonrpc::RawJsonRpcMessage;
use crate::jsonrpc::RawJsonRpcParams;
use crate::jsonrpc::RawJsonRpcResponse as Response;
use crate::jsonrpc::RequestReplyTarget;
use crate::jsonrpc::Responder;
use crate::jsonrpc::ResponseDestination;
use crate::jsonrpc::ResponseDispatch;
use crate::jsonrpc::ResponseRouter;
use crate::jsonrpc::TransportFrame;
use crate::jsonrpc::dynamic_handler::DynHandleDispatchFrom;
use crate::jsonrpc::dynamic_handler::DynamicHandlerMessage;
use crate::jsonrpc::outgoing_actor::send_raw_message;
use crate::jsonrpc::protocol_compat::ProtocolCompat;
use crate::jsonrpc::raw_is_response_only_shape;
use crate::RawJsonRpcId;
use crate::role::Role;
use super::Handled;
pub(super) struct IncomingHandlers<Message, Close> {
messages: Message,
close: Close,
foreground_succeeded: super::SharedCompletionSignal,
}
impl<Message, Close> IncomingHandlers<Message, Close> {
pub(super) fn new(
messages: Message,
close: Close,
foreground_succeeded: super::SharedCompletionSignal,
) -> Self {
Self {
messages,
close,
foreground_succeeded,
}
}
}
pub(super) async fn incoming_protocol_actor<Counterpart: Role>(
counterpart: Counterpart,
connection: &ConnectionTo<Counterpart>,
transport_rx: impl futures::Stream<Item = TransportFrame>,
dynamic_handler_rx: mpsc::UnboundedReceiver<DynamicHandlerMessage<Counterpart>>,
pending_replies: PendingReplies,
handlers: IncomingHandlers<
impl HandleDispatchFrom<Counterpart>,
impl HandleConnectionClose<Counterpart>,
>,
protocol_compat: ProtocolCompat,
) -> Result<(), crate::Error> {
let IncomingHandlers {
messages: mut handler,
close: on_close,
foreground_succeeded,
} = handlers;
let transport_with_close = futures::StreamExt::chain(
transport_rx.map(IncomingProtocolMsg::Transport),
stream::iter([IncomingProtocolMsg::TransportClosed]),
);
let my_rx =
transport_with_close.merge(dynamic_handler_rx.map(IncomingProtocolMsg::DynamicHandler));
let mut my_rx = std::pin::pin!(my_rx);
let mut dynamic_handlers = DynamicHandlers::default();
let mut pending_messages = ParkedMessages::default();
let request_cancellations = super::RequestCancellationRegistry::new();
let mut on_close = Some(on_close);
let mut queued_transport_messages = VecDeque::new();
loop {
let message_result = if let Some(message) = queued_transport_messages.pop_front() {
message
} else {
let Some(message) = my_rx.next().await else {
break;
};
message
};
if foreground_succeeded.clone().now_or_never().is_some() {
break;
}
let event = match &message_result {
IncomingProtocolMsg::Transport(_) => "transport_frame",
IncomingProtocolMsg::TransportClosed => "transport_closed",
IncomingProtocolMsg::DynamicHandler(_) => "dynamic_handler",
};
tracing::trace!(event, actor = "incoming_protocol_actor");
match message_result {
IncomingProtocolMsg::TransportClosed => {
connection.begin_incoming_close();
let pending_reply_count = pending_replies.close_incoming();
tracing::debug!(pending_reply_count, "Incoming transport closed");
let callback_result = on_close
.take()
.expect("incoming transport close handled more than once")
.handle_connection_close(connection.clone())
.await;
connection.finish_incoming_close();
callback_result?;
}
IncomingProtocolMsg::DynamicHandler(message) => {
handle_dynamic_handler_message(
message,
connection,
&mut dynamic_handlers,
&mut pending_messages,
)
.await?;
}
IncomingProtocolMsg::Transport(frame) => {
if let Some((message, destination)) = frame_entry(frame) {
match message {
Ok(RawJsonRpcMessage::Request(request)) => {
tracing::trace!(method = %request.method, id = ?request.id, "Handling request");
let request_method = request.method.to_string();
let request_id = request.id.clone();
let destination = destination
.expect("every incoming request has a response destination");
match dispatch_from_message(
connection,
request.method,
request.params,
Some(request.id),
&protocol_compat,
&request_cancellations,
Some(destination.clone()),
) {
Ok(dispatches) => {
for dispatch in dispatches {
dispatch_dispatch(
counterpart.clone(),
connection,
dispatch,
&mut dynamic_handlers,
&mut handler,
&mut pending_messages,
&request_cancellations,
)
.await?;
}
}
Err(error) => {
handle_handler_error(
connection,
Some(HandlerErrorTarget::Request(RequestReplyTarget {
id: request_id,
method: request_method.clone(),
destination,
})),
request_method,
error,
)?;
}
}
}
Ok(RawJsonRpcMessage::Notification(notification)) => {
tracing::trace!(method = %notification.method, "Handling notification");
let request_method = notification.method.to_string();
match dispatch_from_message(
connection,
notification.method,
notification.params,
None,
&protocol_compat,
&request_cancellations,
None,
) {
Ok(dispatches) => {
for dispatch in dispatches {
dispatch_dispatch(
counterpart.clone(),
connection,
dispatch,
&mut dynamic_handlers,
&mut handler,
&mut pending_messages,
&request_cancellations,
)
.await?;
}
}
Err(error) => {
handle_handler_error(connection, None, request_method, error)?;
}
}
}
Ok(RawJsonRpcMessage::Response(response)) => {
let (id, result) = match response {
Response::Result { id, result } => (id, Ok(result)),
Response::Error { id, error } => (id, Err(error.into_acp_error())),
};
tracing::trace!(?id, "Handling response");
if let Some(pending_reply) = pending_replies.remove(&id) {
let result = protocol_compat.incoming_response(
&pending_reply.method,
pending_reply.accepts_lower_initialize_version,
result,
);
let (dispatch, response_dispatch) =
dispatch_from_response(pending_reply, result);
dispatch_dispatch(
counterpart.clone(),
connection,
dispatch,
&mut dynamic_handlers,
&mut handler,
&mut pending_messages,
&request_cancellations,
)
.await?;
if let Some(ack_rx) = response_dispatch.complete() {
let _ = ack_rx.await;
connection
.dynamic_handler_tx
.unbounded_send(DynamicHandlerMessage::Barrier)
.map_err(crate::util::internal_error)?;
loop {
let Some(message) = my_rx.next().await else {
return Err(crate::util::internal_error(
"dynamic-handler stream closed before its barrier",
));
};
match message {
IncomingProtocolMsg::DynamicHandler(
DynamicHandlerMessage::Barrier,
) => break,
IncomingProtocolMsg::DynamicHandler(message) => {
handle_dynamic_handler_message(
message,
connection,
&mut dynamic_handlers,
&mut pending_messages,
)
.await?;
}
message @ (IncomingProtocolMsg::Transport(_)
| IncomingProtocolMsg::TransportClosed) => {
queued_transport_messages.push_back(message);
}
}
}
}
} else {
tracing::warn!(
?id,
"incoming_actor: received response for unknown id, no subscriber found"
);
}
}
Err(error) => {
tracing::warn!(
error = ?crate::util::redacted_error(error.code),
"Invalid transport input, sending error response"
);
let destination = destination
.expect("invalid request input has a response destination");
send_raw_message(
&connection.message_tx,
OutgoingMessage::UncorrelatedErrorResponse { error, destination },
)?;
}
}
}
}
}
}
Ok(())
}
async fn handle_dynamic_handler_message<Counterpart: Role>(
message: DynamicHandlerMessage<Counterpart>,
connection: &ConnectionTo<Counterpart>,
dynamic_handlers: &mut DynamicHandlers<Counterpart>,
pending_messages: &mut ParkedMessages,
) -> Result<(), crate::Error> {
match message {
DynamicHandlerMessage::AddDynamicHandler(uuid, mut handler, active) => {
let mut new_pending_messages = VecDeque::new();
for (pending_message, reason) in std::mem::take(&mut pending_messages.messages) {
tracing::trace!(method = pending_message.method(), handler = ?handler.dyn_describe_chain(), "Retrying message");
let reply_target = pending_message.handler_error_target();
let method = pending_message.method().to_string();
let _attempt = reply_target
.as_ref()
.and_then(HandlerErrorTarget::begin_handler_attempt);
match handler
.dyn_handle_dispatch_from(pending_message, connection.clone())
.await
{
Ok(Handled::Yes) => {
tracing::trace!("Message handled");
}
Ok(Handled::No {
message: m,
retry: _,
}) => {
tracing::trace!(method = m.method(), handler = ?handler.dyn_describe_chain(), "Message not handled");
new_pending_messages.push_back((m, reason));
}
Err(err) => {
tracing::warn!(%method, err = ?crate::util::redacted_error(err.code), handler = ?handler.dyn_describe_chain(), "Dynamic handler errored on pending message");
handle_handler_error(connection, reply_target, method, err)?;
}
}
}
pending_messages.messages = new_pending_messages;
dynamic_handlers.handlers.insert(uuid, handler);
dynamic_handlers.active.insert(uuid, active);
pending_messages.expire_awaited_requests(dynamic_handlers)?;
}
DynamicHandlerMessage::RemoveDynamicHandler(uuid) => {
dynamic_handlers.handlers.remove(&uuid);
dynamic_handlers.active.remove(&uuid);
pending_messages.expire_awaited_requests(dynamic_handlers)?;
}
DynamicHandlerMessage::Barrier => {}
DynamicHandlerMessage::AcknowledgedBarrier(acknowledgment) => {
let _ = acknowledgment.send(());
}
}
Ok(())
}
#[derive(Debug)]
enum IncomingProtocolMsg<Counterpart: Role> {
Transport(TransportFrame),
TransportClosed,
DynamicHandler(DynamicHandlerMessage<Counterpart>),
}
fn frame_entry(
frame: TransportFrame,
) -> Option<(
Result<RawJsonRpcMessage, crate::Error>,
Option<ResponseDestination>,
)> {
match frame {
TransportFrame::Single(message) => {
let destination = matches!(&message, RawJsonRpcMessage::Request(_))
.then(ResponseDestination::individual);
Some((Ok(message), destination))
}
TransportFrame::Malformed { raw, error } => (!raw_is_response_only_shape(&raw))
.then(|| (Err(error), Some(ResponseDestination::individual()))),
}
}
fn dispatch_from_message<Counterpart: Role>(
connection: &ConnectionTo<Counterpart>,
method: std::sync::Arc<str>,
params: Option<RawJsonRpcParams>,
id: Option<RawJsonRpcId>,
protocol_compat: &ProtocolCompat,
request_cancellations: &super::RequestCancellationRegistry,
response_destination: Option<ResponseDestination>,
) -> Result<Vec<Dispatch>, crate::Error> {
let message = UntypedMessage::new(&method, crate::jsonrpc::params_from_transport(params))
.expect("well-formed JSON");
if let Some(id) = id {
let response_destination =
response_destination.expect("incoming requests always have a response destination");
let message = protocol_compat.incoming_request(&id, message)?;
Ok(vec![Dispatch::Request(
message,
Responder::new(
connection.message_tx.clone(),
connection.incoming_closed.clone(),
method.to_string(),
id,
request_cancellations,
response_destination,
),
)])
} else {
debug_assert!(response_destination.is_none());
Ok(protocol_compat
.incoming_notification(message)?
.into_iter()
.map(Dispatch::Notification)
.collect())
}
}
pub(super) fn dispatch_from_response(
pending_reply: PendingReply,
result: Result<serde_json::Value, crate::Error>,
) -> (Dispatch, ResponseDispatch) {
let response_dispatch = ResponseDispatch::default();
let router = ResponseRouter::new(pending_reply, response_dispatch.clone());
(Dispatch::Response(result, router), response_dispatch)
}
#[tracing::instrument(
skip(connection, dispatch, dynamic_handlers, handler, pending_messages),
fields(method = dispatch.method()),
level = "trace",
)]
async fn dispatch_dispatch<Counterpart: Role>(
counterpart: Counterpart,
connection: &ConnectionTo<Counterpart>,
mut dispatch: Dispatch,
dynamic_handlers: &mut DynamicHandlers<Counterpart>,
handler: &mut impl HandleDispatchFrom<Counterpart>,
pending_messages: &mut ParkedMessages,
request_cancellations: &super::RequestCancellationRegistry,
) -> Result<(), crate::Error> {
tracing::trace!(method = dispatch.method(), id = ?dispatch.raw_id(), "dispatch_dispatch");
let mut retry_requested = false;
let id = dispatch.raw_id().cloned();
let method = dispatch.method().to_string();
let error_target = dispatch.handler_error_target();
let _attempt = error_target
.as_ref()
.and_then(HandlerErrorTarget::begin_handler_attempt);
match request_cancellations.cancel_if_requested(&dispatch) {
Ok(true) => {
tracing::debug!(?method, "Marked request as cancelled");
}
Ok(false) => {}
Err(err) => {
tracing::warn!(
?method,
?id,
err = ?crate::util::redacted_error(err.code),
"Request cancellation notification errored"
);
return handle_handler_error(connection, error_target, method, err);
}
}
tracing::trace!(handler = ?handler.describe_chain(), "Attempting handler chain");
match handler
.handle_dispatch_from(dispatch, connection.clone())
.await
{
Ok(Handled::Yes) => {
tracing::trace!(?method, ?id, handler = ?handler.describe_chain(), "Handler accepted message");
return Ok(());
}
Ok(Handled::No { message: m, retry }) => {
tracing::trace!(?method, ?id, handler = ?handler.describe_chain(), "Handler declined message");
dispatch = m;
retry_requested |= retry;
}
Err(err) => {
tracing::warn!(?method, ?id, err = ?crate::util::redacted_error(err.code), handler = ?handler.describe_chain(), "Handler errored");
return handle_handler_error(connection, error_target, method, err);
}
}
for dynamic_handler in dynamic_handlers.handlers.values_mut() {
tracing::trace!(handler = ?dynamic_handler.dyn_describe_chain(), "Attempting dynamic handler");
match dynamic_handler
.dyn_handle_dispatch_from(dispatch, connection.clone())
.await
{
Ok(Handled::Yes) => {
tracing::trace!(?method, ?id, handler = ?dynamic_handler.dyn_describe_chain(), "Dynamic handler accepted message");
return Ok(());
}
Ok(Handled::No { message: m, retry }) => {
tracing::trace!(?method, ?id, handler = ?dynamic_handler.dyn_describe_chain(), "Dynamic handler declined message");
retry_requested |= retry;
dispatch = m;
}
Err(err) => {
tracing::warn!(?method, ?id, err = ?crate::util::redacted_error(err.code), handler = ?dynamic_handler.dyn_describe_chain(), "Dynamic handler errored");
return handle_handler_error(connection, error_target, method, err);
}
}
}
tracing::trace!(role = ?counterpart, "Attempting default handler");
let role_retry = match counterpart
.default_handle_dispatch_from(dispatch, connection.clone())
.await
{
Ok(Handled::Yes) => {
tracing::trace!(?method, handler = "default", "Role accepted message");
return Ok(());
}
Ok(Handled::No { message: m, retry }) => {
tracing::trace!(?method, handler = "default", "Role declined message");
dispatch = m;
retry
}
Err(err) => {
tracing::warn!(
?method,
?id,
err = ?crate::util::redacted_error(err.code),
handler = "default",
"Default handler errored"
);
return handle_handler_error(connection, error_target, method, err);
}
};
let reason = if retry_requested {
Some(ParkReason::Requested)
} else if !role_retry {
None
} else if !matches!(dispatch, Dispatch::Request(..)) {
Some(ParkReason::Requested)
} else if dynamic_handlers.any_inactive() {
Some(ParkReason::AwaitingHandlerChange)
} else {
None
};
match reason {
Some(reason) => {
tracing::debug!(
?method,
"Retrying message as new dynamic handlers are added"
);
pending_messages.park(dispatch, reason)
}
None => reject_unhandled(dispatch),
}
}
fn reject_unhandled(dispatch: Dispatch) -> Result<(), crate::Error> {
let method = dispatch.method().to_string();
match dispatch {
Dispatch::Notification(_) => {
tracing::debug!(?method, "Ignoring unhandled notification");
Ok(())
}
Dispatch::Request(_, responder) => {
tracing::info!(?method, "Rejecting request with error, no handler");
#[cfg(feature = "unstable_mcp_over_acp")]
if method == "mcp/message" {
return responder.respond_with_error(crate::Error::new(
crate::mcp_server::MCP_SERVER_UNAVAILABLE,
"MCP server registration unavailable",
));
}
responder.respond_with_error(crate::Error::method_not_found().data(method))
}
Dispatch::Response(result, router) => {
tracing::trace!(?method, "Forwarding response");
router.route_with_result(result)
}
}
}
pub(super) const MAX_PARKED_MESSAGES: usize = 1024;
struct DynamicHandlers<Counterpart: Role> {
handlers: FxHashMap<Uuid, Box<dyn DynHandleDispatchFrom<Counterpart>>>,
active: FxHashMap<Uuid, Arc<AtomicBool>>,
}
impl<Counterpart: Role> Default for DynamicHandlers<Counterpart> {
fn default() -> Self {
Self {
handlers: FxHashMap::default(),
active: FxHashMap::default(),
}
}
}
impl<Counterpart: Role> DynamicHandlers<Counterpart> {
fn any_inactive(&self) -> bool {
self.active
.values()
.any(|active| !active.load(Ordering::Acquire))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ParkReason {
Requested,
AwaitingHandlerChange,
}
#[derive(Default)]
struct ParkedMessages {
messages: VecDeque<(Dispatch, ParkReason)>,
}
impl ParkedMessages {
fn park(&mut self, dispatch: Dispatch, reason: ParkReason) -> Result<(), crate::Error> {
if self.messages.len() >= MAX_PARKED_MESSAGES
&& let Some((oldest, _)) = self.messages.pop_front()
{
tracing::warn!(
method = oldest.method(),
limit = MAX_PARKED_MESSAGES,
"Parked-message limit reached, giving up on the oldest message"
);
reject_unhandled(oldest)?;
}
self.messages.push_back((dispatch, reason));
Ok(())
}
fn expire_awaited_requests<Counterpart: Role>(
&mut self,
dynamic_handlers: &DynamicHandlers<Counterpart>,
) -> Result<(), crate::Error> {
if dynamic_handlers.any_inactive()
|| !self
.messages
.iter()
.any(|(_, reason)| *reason == ParkReason::AwaitingHandlerChange)
{
return Ok(());
}
let mut kept = VecDeque::with_capacity(self.messages.len());
for (dispatch, reason) in std::mem::take(&mut self.messages) {
if reason == ParkReason::AwaitingHandlerChange {
reject_unhandled(dispatch)?;
} else {
kept.push_back((dispatch, reason));
}
}
self.messages = kept;
Ok(())
}
}
fn handle_handler_error<Counterpart: Role>(
connection: &ConnectionTo<Counterpart>,
target: Option<HandlerErrorTarget>,
method: String,
error: crate::Error,
) -> Result<(), crate::Error> {
match target {
Some(HandlerErrorTarget::Request(reply_target)) => {
reply_target.destination.discard_abandoned();
send_raw_message(
&connection.message_tx,
OutgoingMessage::Response {
id: reply_target.id,
method: reply_target.method,
response: Err(error),
destination: reply_target.destination,
},
)
}
Some(HandlerErrorTarget::Response(reply_target)) => {
reply_target.route(Err(error));
Ok(())
}
None => {
tracing::warn!(
%method,
error = ?crate::util::redacted_error(error.code),
"Ignoring message-processing error because there is no request to answer"
);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use futures::StreamExt as _;
use serde_json::{Value, json};
use crate::{
Agent, Builder, Channel, Client, ConnectionTo, Dispatch, HandleDispatchFrom, Handled,
TransportFrame,
};
struct AnswerSession(&'static str);
impl HandleDispatchFrom<Agent> for AnswerSession {
fn handle_dispatch_from(
&mut self,
message: Dispatch,
_connection: ConnectionTo<Agent>,
) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
std::future::ready(match message {
Dispatch::Request(request, responder) if request.params()["sessionId"] == "s" => {
responder
.respond(json!({ "by": self.0 }))
.map(|()| Handled::Yes)
}
message => Ok(Handled::No {
message,
retry: false,
}),
})
}
fn describe_chain(&self) -> impl std::fmt::Debug {
self.0
}
}
fn send(peer: &Channel, message: &Value) {
peer.tx
.unbounded_send(TransportFrame::parse_json(&message.to_string()))
.expect("connection is reading");
}
async fn recv(peer: &mut Channel) -> Value {
let frame = tokio::time::timeout(std::time::Duration::from_secs(5), peer.rx.next())
.await
.expect("the connection should answer in time")
.expect("the connection is open");
serde_json::from_str(&frame.to_json().unwrap()).unwrap()
}
async fn park_session_request(peer: &mut Channel) {
send(
peer,
&json!({ "jsonrpc": "2.0", "id": 1, "method": "_test/ask", "params": { "sessionId": "s" } }),
);
send(
peer,
&json!({ "jsonrpc": "2.0", "id": 2, "method": "_test/unhandled", "params": {} }),
);
let response = recv(peer).await;
assert_eq!(response["id"], 2, "{response}");
assert_eq!(response["error"]["code"], -32601);
}
#[tokio::test(flavor = "current_thread")]
async fn session_request_waits_for_a_replacement_handler() {
let (transport, mut peer) = Channel::duplex();
Builder::new(Client)
.connect_with(transport, async |cx: ConnectionTo<Agent>| {
let old = cx.add_dynamic_handler(AnswerSession("old"))?;
cx.dynamic_handler_barrier().await?;
old.deactivate();
park_session_request(&mut peer).await;
cx.add_dynamic_handler(AnswerSession("new"))?.detach();
drop(old);
let response = recv(&mut peer).await;
assert_eq!(response["id"], 1);
assert_eq!(response["result"]["by"], "new");
Ok(())
})
.await
.expect("connection runs");
}
#[tokio::test(flavor = "current_thread")]
async fn session_request_is_answered_when_no_replacement_comes() {
let (transport, mut peer) = Channel::duplex();
Builder::new(Client)
.connect_with(transport, async |cx: ConnectionTo<Agent>| {
let old = cx.add_dynamic_handler(AnswerSession("old"))?;
cx.dynamic_handler_barrier().await?;
old.deactivate();
park_session_request(&mut peer).await;
drop(old);
let response = recv(&mut peer).await;
assert_eq!(response["id"], 1);
assert_eq!(response["error"]["code"], -32601);
assert_eq!(response["error"]["data"], "_test/ask");
Ok(())
})
.await
.expect("connection runs");
}
}