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 opentelemetry::Context as TelemetryContext;
#[cfg(feature = "graphql")]
use surrealdb_core::gql::NotificationRouter;
use surrealdb_core::kvs::Datastore;
use surrealdb_core::rpc::{DbResponse, DbResult};
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;
use crate::telemetry::metrics::ws::NotificationContext;
static CONN_CLOSED_ERR: &str = "Connection closed normally";
type WebSocket = Arc<Websocket>;
type WebSockets = RwLock<HashMap<Uuid, WebSocket>>;
type LiveQueries = RwLock<HashMap<Uuid, (Uuid, Option<Uuid>)>>;
pub struct RpcState {
pub web_sockets: WebSockets,
pub live_queries: LiveQueries,
pub http: Arc<crate::rpc::http::Http>,
#[cfg(feature = "graphql")]
pub(crate) notification_router: Arc<NotificationRouter>,
}
impl RpcState {
pub fn new(
datastore: Arc<surrealdb_core::kvs::Datastore>,
session: surrealdb_core::dbs::Session,
) -> Self {
Self {
web_sockets: RwLock::new(HashMap::new()),
live_queries: RwLock::new(HashMap::new()),
http: Arc::new(crate::rpc::http::Http::new(datastore, session)),
#[cfg(feature = "graphql")]
notification_router: Arc::new(NotificationRouter::new(
*GQL_SUBSCRIPTION_CHANNEL_CAPACITY,
)),
}
}
}
pub(crate) async fn notifications(
ds: Arc<Datastore>,
state: Arc<RpcState>,
canceller: CancellationToken,
) {
let mut futures = FuturesUnordered::new();
if let Some(channel) = ds.notifications() {
loop {
tokio::select! {
biased;
_ = canceller.cancelled() => break,
Some(_) = futures.next() => continue,
Ok(notification) = channel.recv() => {
#[cfg(feature = "graphql")]
if state.notification_router.has_subscribers() {
state.notification_router.dispatch(¬ification);
}
let id = notification.id.as_ref();
let websocket = {
state.live_queries.read().await.get(id).copied()
};
if let Some((id, session_id)) = websocket.as_ref() {
let websocket = {
state.web_sockets.read().await.get(id).cloned()
};
if let Some(rpc) = websocket {
let message = DbResponse::success(None, session_id.map(Into::into), DbResult::Live(notification));
let cx = TelemetryContext::new();
let not_ctx = NotificationContext::default()
.with_live_id(id.to_string());
let cx = Arc::new(cx.with_value(not_ctx));
let format = rpc.format;
let sender = rpc.channel.clone();
let future = crate::rpc::response::send(message, cx, format, sender);
futures.push(future);
}
}
},
}
}
}
}
pub(crate) 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(crate) fn shutdown(state: Arc<RpcState>) {
if let Ok(mut writer) = state.web_sockets.try_write() {
writer.drain();
}
}