use crate::types::{
Message, RequestId, SubscriptionFilter, SubscriptionMeta, Uri, notification::Notification,
subscription::is_subscribable,
};
use dashmap::DashMap;
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
use tokio::sync::mpsc::{OwnedPermit, Sender};
use tokio_util::sync::CancellationToken;
pub(crate) const DEFAULT_SUBSCRIPTION_CAPACITY: usize = 64;
type Key = u64;
#[derive(Debug)]
struct Subscription {
id: RequestId,
session_id: Option<uuid::Uuid>,
accepted: SubscriptionFilter,
sink: Sender<Message>,
token: CancellationToken,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct SubscriptionRegistry {
entries: Arc<DashMap<Key, Subscription>>,
next_key: Arc<AtomicU64>,
}
#[derive(Debug)]
pub(crate) struct SubscriptionGuard {
key: Key,
registry: SubscriptionRegistry,
}
impl Drop for SubscriptionGuard {
fn drop(&mut self) {
self.registry.entries.remove(&self.key);
}
}
impl SubscriptionRegistry {
pub(crate) fn register(
&self,
id: RequestId,
session_id: Option<uuid::Uuid>,
accepted: SubscriptionFilter,
sink: Sender<Message>,
ack: Message,
ack_slot: OwnedPermit<Message>,
) -> (CancellationToken, SubscriptionGuard) {
let token = CancellationToken::new();
let key = self.next_key.fetch_add(1, Ordering::Relaxed);
let slot = self.entries.entry(key);
ack_slot.send(ack);
slot.insert(Subscription {
id,
session_id,
accepted,
sink,
token: token.clone(),
});
(
token,
SubscriptionGuard {
key,
registry: self.clone(),
},
)
}
pub(crate) fn cancel(&self, id: &RequestId) -> bool {
let mut found = false;
for entry in self.entries.iter() {
if entry.session_id.is_none() && entry.id == *id {
entry.token.cancel();
found = true;
}
}
found
}
pub(crate) fn is_resource_subscribed(&self, uri: &Uri) -> bool {
self.entries
.iter()
.any(|e| e.accepted.resource_subscriptions.contains(uri))
}
pub(crate) fn broadcast(&self, method: &str, params: Option<&serde_json::Value>) -> bool {
if !is_subscribable(method) {
return false;
}
let uri = params
.and_then(|p| p.get("uri"))
.and_then(|u| u.as_str())
.map(Uri::from);
for entry in self.entries.iter() {
if !entry.accepted.matches(method, uri.as_ref()) {
continue;
}
let notification = Notification::new(method, Some(tag(params, entry.id.clone())));
if entry
.sink
.try_send(Message::Notification(notification))
.is_err()
{
#[cfg(feature = "tracing")]
tracing::warn!(
logger = "neva",
method,
subscription = %entry.id,
"dropped a notification: the subscription stream is full or closed"
);
}
}
true
}
}
fn tag(params: Option<&serde_json::Value>, id: RequestId) -> serde_json::Value {
let mut value = params.cloned().unwrap_or_else(|| serde_json::json!({}));
let meta =
serde_json::to_value(SubscriptionMeta::new(id)).unwrap_or_else(|_| serde_json::json!({}));
match value.as_object_mut() {
Some(obj) => {
obj.insert("_meta".to_owned(), meta);
value
}
None => serde_json::json!({ "_meta": meta }),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{prompt, resource, subscription::SUBSCRIPTION_ID_KEY, tool};
use tokio::sync::mpsc::channel;
async fn registry_with(
id: RequestId,
accepted: SubscriptionFilter,
) -> (
SubscriptionRegistry,
tokio::sync::mpsc::Receiver<Message>,
SubscriptionGuard,
) {
let registry = SubscriptionRegistry::default();
let (tx, mut rx) = channel::<Message>(8);
let (_token, guard) = register(®istry, id, None, accepted, tx, &mut rx).await;
(registry, rx, guard)
}
async fn register(
registry: &SubscriptionRegistry,
id: RequestId,
session_id: Option<uuid::Uuid>,
accepted: SubscriptionFilter,
tx: Sender<Message>,
rx: &mut tokio::sync::mpsc::Receiver<Message>,
) -> (CancellationToken, SubscriptionGuard) {
let slot = tx
.clone()
.reserve_owned()
.await
.expect("the test sink must have room for the acknowledgment");
let registered = registry.register(id, session_id, accepted, tx, ack(), slot);
let first = rx
.try_recv()
.expect("register must queue the acknowledgment");
assert_eq!(
method_of(&first),
crate::types::subscription::commands::ACKNOWLEDGED,
"the acknowledgment must be the first message on the stream"
);
registered
}
fn ack() -> Message {
Message::Notification(Notification::new(
crate::types::subscription::commands::ACKNOWLEDGED,
None,
))
}
fn method_of(msg: &Message) -> String {
serde_json::to_value(msg).unwrap()["method"]
.as_str()
.unwrap()
.to_owned()
}
#[tokio::test]
async fn it_delivers_only_subscribed_types() {
let (registry, mut rx, _guard) = registry_with(
RequestId::Number(1),
SubscriptionFilter::new().with_tools_changed(),
)
.await;
assert!(registry.broadcast(prompt::commands::LIST_CHANGED, None));
assert!(registry.broadcast(tool::commands::LIST_CHANGED, None));
let msg = rx.try_recv().expect("the tools notification is subscribed");
assert_eq!(method_of(&msg), tool::commands::LIST_CHANGED);
assert!(
rx.try_recv().is_err(),
"a type the client never requested must not be delivered"
);
}
#[tokio::test]
async fn it_tags_every_notification_with_its_subscription_id() {
let (registry, mut rx, _guard) = registry_with(
RequestId::String("sub-1".into()),
SubscriptionFilter::new().with_tools_changed(),
)
.await;
registry.broadcast(tool::commands::LIST_CHANGED, None);
let msg = rx.try_recv().unwrap();
let json = serde_json::to_value(&msg).unwrap();
assert_eq!(json["params"]["_meta"][SUBSCRIPTION_ID_KEY], "sub-1");
}
#[tokio::test]
async fn it_keeps_resource_update_params_alongside_the_tag() {
let (registry, mut rx, _guard) = registry_with(
RequestId::Number(1),
SubscriptionFilter::new().with_resource("res://a"),
)
.await;
let params = serde_json::json!({ "uri": "res://a" });
registry.broadcast(resource::commands::UPDATED, Some(¶ms));
let json = serde_json::to_value(rx.try_recv().unwrap()).unwrap();
assert_eq!(json["params"]["uri"], "res://a");
assert_eq!(json["params"]["_meta"][SUBSCRIPTION_ID_KEY], 1);
}
#[tokio::test]
async fn it_routes_resource_updates_by_uri() {
let (registry, mut rx, _guard) = registry_with(
RequestId::Number(1),
SubscriptionFilter::new().with_resource("res://a"),
)
.await;
let other = serde_json::json!({ "uri": "res://b" });
registry.broadcast(resource::commands::UPDATED, Some(&other));
assert!(
rx.try_recv().is_err(),
"an update for an unsubscribed URI must not be delivered"
);
}
#[tokio::test]
async fn it_reports_non_subscribable_methods() {
let registry = SubscriptionRegistry::default();
assert!(!registry.broadcast("notifications/progress", None));
assert!(!registry.broadcast("notifications/tasks/status", None));
}
#[tokio::test]
async fn it_fans_out_to_concurrent_subscriptions() {
let registry = SubscriptionRegistry::default();
let (tx1, mut rx1) = channel::<Message>(8);
let (tx2, mut rx2) = channel::<Message>(8);
let (_t1, _g1) = register(
®istry,
RequestId::Number(1),
None,
SubscriptionFilter::new().with_tools_changed(),
tx1,
&mut rx1,
)
.await;
let (_t2, _g2) = register(
®istry,
RequestId::Number(2),
None,
SubscriptionFilter::new().with_prompts_changed(),
tx2,
&mut rx2,
)
.await;
registry.broadcast(tool::commands::LIST_CHANGED, None);
let json = serde_json::to_value(rx1.try_recv().unwrap()).unwrap();
assert_eq!(json["params"]["_meta"][SUBSCRIPTION_ID_KEY], 1);
assert!(rx2.try_recv().is_err());
}
#[tokio::test]
async fn it_cancels_only_the_named_subscription() {
let registry = SubscriptionRegistry::default();
let (tx1, mut rx1) = channel::<Message>(8);
let (tx2, mut rx2) = channel::<Message>(8);
let (token1, _g1) = register(
®istry,
RequestId::Number(1),
None,
SubscriptionFilter::new(),
tx1,
&mut rx1,
)
.await;
let (token2, _g2) = register(
®istry,
RequestId::Number(2),
None,
SubscriptionFilter::new(),
tx2,
&mut rx2,
)
.await;
assert!(registry.cancel(&RequestId::Number(1)));
assert!(token1.is_cancelled());
assert!(!token2.is_cancelled());
assert!(!registry.cancel(&RequestId::Number(3)));
}
#[tokio::test]
async fn it_deregisters_on_guard_drop() {
let registry = SubscriptionRegistry::default();
let (tx, mut rx) = channel::<Message>(8);
let (_token, guard) = register(
®istry,
RequestId::Number(1),
None,
SubscriptionFilter::new().with_tools_changed(),
tx,
&mut rx,
)
.await;
drop(guard);
registry.broadcast(tool::commands::LIST_CHANGED, None);
assert!(rx.try_recv().is_err());
assert!(!registry.cancel(&RequestId::Number(1)));
}
#[tokio::test]
async fn it_keeps_two_clients_that_picked_the_same_id_apart() {
let registry = SubscriptionRegistry::default();
let (tx_a, mut rx_a) = channel::<Message>(8);
let (tx_b, mut rx_b) = channel::<Message>(8);
let (_ta, _ga) = register(
®istry,
RequestId::Number(1),
Some(uuid::Uuid::new_v4()),
SubscriptionFilter::new().with_tools_changed(),
tx_a,
&mut rx_a,
)
.await;
let (_tb, _gb) = register(
®istry,
RequestId::Number(1),
Some(uuid::Uuid::new_v4()),
SubscriptionFilter::new().with_tools_changed(),
tx_b,
&mut rx_b,
)
.await;
registry.broadcast(tool::commands::LIST_CHANGED, None);
for rx in [&mut rx_a, &mut rx_b] {
let json = serde_json::to_value(rx.try_recv().unwrap()).unwrap();
assert_eq!(json["params"]["_meta"][SUBSCRIPTION_ID_KEY], 1);
}
}
#[tokio::test]
async fn it_does_not_deregister_a_colliding_id_on_teardown() {
let registry = SubscriptionRegistry::default();
let (tx_a, mut rx_a) = channel::<Message>(8);
let (tx_b, mut rx_b) = channel::<Message>(8);
let (_ta, guard_a) = register(
®istry,
RequestId::Number(1),
Some(uuid::Uuid::new_v4()),
SubscriptionFilter::new().with_tools_changed(),
tx_a,
&mut rx_a,
)
.await;
let (_tb, _gb) = register(
®istry,
RequestId::Number(1),
Some(uuid::Uuid::new_v4()),
SubscriptionFilter::new().with_tools_changed(),
tx_b,
&mut rx_b,
)
.await;
drop(guard_a);
registry.broadcast(tool::commands::LIST_CHANGED, None);
assert!(
rx_b.try_recv().is_ok(),
"the surviving subscription must still receive"
);
}
#[tokio::test]
async fn it_refuses_to_cancel_a_session_bound_subscription() {
let registry = SubscriptionRegistry::default();
let (tx_a, mut rx_a) = channel::<Message>(8);
let (tx_b, mut rx_b) = channel::<Message>(8);
let (token_a, _ga) = register(
®istry,
RequestId::Number(1),
Some(uuid::Uuid::new_v4()),
SubscriptionFilter::new(),
tx_a,
&mut rx_a,
)
.await;
let (token_b, _gb) = register(
®istry,
RequestId::Number(1),
Some(uuid::Uuid::new_v4()),
SubscriptionFilter::new(),
tx_b,
&mut rx_b,
)
.await;
assert!(!registry.cancel(&RequestId::Number(1)));
assert!(!token_a.is_cancelled());
assert!(!token_b.is_cancelled());
}
#[tokio::test]
async fn it_answers_whether_a_resource_is_watched() {
let (registry, _rx, _guard) = registry_with(
RequestId::Number(1),
SubscriptionFilter::new().with_resource("res://a"),
)
.await;
assert!(registry.is_resource_subscribed(&Uri::from("res://a")));
assert!(!registry.is_resource_subscribed(&Uri::from("res://b")));
}
}