pub mod format;
pub mod http;
pub mod response;
pub mod websocket;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use futures::stream::FuturesUnordered;
use surrealdb_core::channel::Receiver;
#[cfg(feature = "graphql")]
use surrealdb_core::gql::NotificationRouter;
use surrealdb_core::rpc::{DbResponse, DbResult};
use surrealdb_types::Notification;
use tokio::sync::RwLock;
use tokio_stream::StreamExt;
use tokio_util::sync::CancellationToken;
use uuid::Uuid;
#[cfg(feature = "graphql")]
use crate::cnf::GQL_SUBSCRIPTION_CHANNEL_CAPACITY;
use crate::rpc::websocket::Websocket;
static CONN_CLOSED_ERR: &str = "Connection closed normally";
type WebSocket = Arc<Websocket>;
type WebSockets = RwLock<HashMap<Uuid, WebSocket>>;
#[derive(Clone, Debug)]
pub struct LiveQueryEntry {
pub websocket_id: Uuid,
pub session_id: Uuid,
pub namespace: Option<String>,
pub database: Option<String>,
}
type LiveQueries = RwLock<HashMap<Uuid, LiveQueryEntry>>;
pub struct RpcState {
pub web_sockets: WebSockets,
pub live_queries: LiveQueries,
pub http: Arc<crate::rpc::http::Http>,
pub metrics_observer: Option<Arc<crate::observe::metrics::MetricsObserver>>,
#[cfg(feature = "graphql")]
pub(crate) notification_router: Arc<NotificationRouter>,
}
impl RpcState {
pub fn new(datastore: Arc<surrealdb_core::kvs::Datastore>) -> Self {
Self::new_with_metrics(datastore, None)
}
pub fn new_with_metrics(
datastore: Arc<surrealdb_core::kvs::Datastore>,
metrics_observer: Option<Arc<crate::observe::metrics::MetricsObserver>>,
) -> Self {
Self {
web_sockets: RwLock::new(HashMap::new()),
live_queries: RwLock::new(HashMap::new()),
http: Arc::new(crate::rpc::http::Http::new(datastore)),
metrics_observer,
#[cfg(feature = "graphql")]
notification_router: Arc::new(NotificationRouter::new(
*GQL_SUBSCRIPTION_CHANNEL_CAPACITY,
)),
}
}
}
pub async fn dispatch_live_notification(notification: Notification, state: Arc<RpcState>) {
#[cfg(feature = "graphql")]
if state.notification_router.has_subscribers() {
state.notification_router.dispatch(¬ification);
}
let live_query = state.live_queries.read().await.get(¬ification.id).cloned();
if let Some(entry) = live_query
&& let Some(rpc) = state.web_sockets.read().await.get(&entry.websocket_id).cloned()
{
if let Some(obs) = state.metrics_observer.as_ref() {
obs.record_live_query_notification(
entry.namespace.as_deref(),
entry.database.as_deref(),
);
}
let wire_session_id = (entry.session_id != rpc.id).then_some(entry.session_id);
let message = DbResponse::success(None, wire_session_id, DbResult::Live(notification));
let format = rpc.format;
let sender = rpc.channel.clone();
crate::rpc::response::send(message, format, sender).await;
}
}
pub async fn notifications(
channel: Receiver<Notification>,
state: Arc<RpcState>,
canceller: CancellationToken,
) {
let mut futures = FuturesUnordered::new();
loop {
tokio::select! {
biased;
_ = canceller.cancelled() => break,
Some(_) = futures.next() => continue,
Ok(notification) = channel.recv() => {
futures.push(dispatch_live_notification(notification, Arc::clone(&state)));
},
}
}
}
pub async fn graceful_shutdown(state: Arc<RpcState>) {
for (_, rpc) in state.web_sockets.read().await.iter() {
rpc.shutdown.cancel();
}
while !state.web_sockets.read().await.is_empty() {
tokio::time::sleep(Duration::from_millis(250)).await;
}
}
pub fn shutdown(state: &Arc<RpcState>) {
if let Ok(mut writer) = state.web_sockets.try_write() {
writer.drain();
}
}