use crate::{stream::websocket_transport::WebsocketTransport, StreamError, StreamResult};
use diem_json_rpc_types::{
stream::{
request::{StreamMethodRequest, SubscribeToEventsParams, SubscribeToTransactionsParams},
response::StreamJsonRpcResponse,
},
Id,
};
use diem_types::event::EventKey;
use futures::Stream;
use std::{
collections::HashMap,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use tokio::{
sync::{mpsc, RwLock},
time::timeout,
};
use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
use tracing::{debug, trace, warn};
pub(crate) type StreamingClientReceiver = mpsc::Receiver<StreamResult<StreamJsonRpcResponse>>;
pub(crate) type StreamingClientSender = mpsc::Sender<StreamResult<StreamJsonRpcResponse>>;
#[allow(dead_code)]
struct SubscriptionSender {
pub id: Id,
pub sender: StreamingClientSender,
}
impl SubscriptionSender {
pub fn new(id: Id, sender: StreamingClientSender) -> Self {
Self { id, sender }
}
}
pub struct SubscriptionStream {
id: Id,
stream: StreamingClientReceiver,
client: StreamingClient,
}
impl SubscriptionStream {
fn new(id: Id, stream: StreamingClientReceiver, client: StreamingClient) -> Self {
Self { id, stream, client }
}
pub fn id(&self) -> &Id {
&self.id
}
pub async fn wait_for_msg(&mut self) -> StreamResult<StreamResult<StreamJsonRpcResponse>> {
match self.stream.recv().await {
None => Err(StreamError::connection_closed(None::<StreamError>)),
Some(msg) => Ok(msg),
}
}
}
impl Drop for SubscriptionStream {
fn drop(&mut self) {
let mut client = self.client.clone();
let id = self.id.clone();
self.stream.close();
tokio::task::spawn(async move {
client.clear_subscription(&id).await;
let _ = client.send_unsubscribe(&id).await;
});
}
}
impl Stream for SubscriptionStream {
type Item = StreamResult<StreamJsonRpcResponse>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.stream.poll_recv(cx)
}
}
pub struct StreamingClientConfig {
pub channel_size: usize,
pub ok_timeout_millis: u64,
}
impl Default for StreamingClientConfig {
fn default() -> Self {
Self {
channel_size: 10,
ok_timeout_millis: 1_000,
}
}
}
#[derive(Clone)]
pub struct StreamingClient {
client: Arc<RwLock<WebsocketTransport>>,
subscriptions: Arc<RwLock<HashMap<Id, SubscriptionSender>>>,
stream: Arc<RwLock<StreamingClientReceiver>>,
config: Arc<StreamingClientConfig>,
}
impl StreamingClient {
pub async fn new<T: Into<String>>(
url: T,
config: StreamingClientConfig,
websocket_config: Option<WebSocketConfig>,
) -> StreamResult<Self> {
let client = WebsocketTransport::new(url, websocket_config).await?;
let subscriptions = Arc::new(RwLock::new(HashMap::new()));
let (stream, client) = client.get_stream();
let mut sct = Self {
client: Arc::new(RwLock::new(client)),
subscriptions,
stream: Arc::new(RwLock::new(stream)),
config: Arc::new(config),
};
sct.start_channel_task();
Ok(sct)
}
pub async fn subscribe_transactions(
&mut self,
starting_version: u64,
include_events: Option<bool>,
) -> StreamResult<SubscriptionStream> {
let request = StreamMethodRequest::SubscribeToTransactions(SubscribeToTransactionsParams {
starting_version,
include_events,
});
self.send_subscription(request).await
}
pub async fn subscribe_events(
&mut self,
event_key: EventKey,
event_seq_num: u64,
) -> StreamResult<SubscriptionStream> {
let request = StreamMethodRequest::SubscribeToEvents(SubscribeToEventsParams {
event_key,
event_seq_num,
});
self.send_subscription(request).await
}
pub(crate) async fn send_unsubscribe(&mut self, id: &Id) -> StreamResult<()> {
debug!("StreamingClient sending unsubscribe for: {:?}", id);
self.client
.write()
.await
.send_method_request(StreamMethodRequest::Unsubscribe, Some(id.clone()))
.await?;
Ok(())
}
pub async fn send_subscription(
&mut self,
request: StreamMethodRequest,
) -> StreamResult<SubscriptionStream> {
let mut subscription_stream = self.get_and_register_id().await?;
let res = self
.client
.write()
.await
.send_method_request(request, Some(subscription_stream.id().clone()))
.await;
let id = match res {
Ok(id) => id,
Err(e) => {
self.clear_subscription(subscription_stream.id()).await;
return Err(e);
}
};
debug!("StreamingClient starting OkTimeout task for id: {:?}", &id);
let duration = Duration::from_millis(self.config.ok_timeout_millis);
let msg = match timeout(duration, subscription_stream.wait_for_msg()).await {
Ok(res) => res??,
Err(_) => {
debug!("StreamingClient OkTimeout for id: {:?}", &id);
self.clear_subscription(&id).await;
return Err(StreamError::subscription_ok_timeout());
}
};
if let Some(err) = msg.error {
self.clear_subscription(&id).await;
return Err(StreamError::subscription_json_rpc_error(err));
}
Ok(subscription_stream)
}
pub async fn subscription_count(&self) -> usize {
self.subscriptions.read().await.len()
}
async fn handle_next_message(&mut self) -> StreamResult<()> {
let msg = self.stream.write().await.recv().await;
trace!("StreamingClient got message: {:?}", &msg);
let msg = match msg {
None => return Err(StreamError::connection_closed(None::<StreamError>)),
Some(msg) => msg,
};
let msg = match msg {
Ok(msg) => msg,
Err(e) => {
warn!("StreamingClient received error on channel: {:?}", e);
return Ok(());
}
};
let id = match &msg.id {
Some(id) => id,
None => {
warn!("StreamingClient got message without an ID: {:?}", &msg);
return Ok(());
}
};
let msg_is_unsubscribe = msg
.result
.as_ref()
.map_or(false, |v| v.get("unsubscribe").is_some());
let id = id.clone();
let subscriptions = self.subscriptions.read().await;
match subscriptions.get(&id) {
Some(sender) => match sender.sender.send(Ok(msg.clone())).await {
Err(e) => {
warn!(error=?&e, "StreamingClient could not forward message: {:?}", &msg);
drop(subscriptions);
if !msg_is_unsubscribe {
let _ = self.send_unsubscribe(&id).await;
}
Ok(())
}
Ok(_) => {
debug!("StreamingClient forwarded message: {:?}", &msg);
Ok(())
}
},
None => {
if !msg_is_unsubscribe {
warn!(
"StreamingClient got message without subscription: {:?}",
&msg
);
drop(subscriptions);
let _ = self.send_unsubscribe(&id).await;
}
Ok(())
}
}
}
async fn register_subscription(&self, id: Id) -> StreamResult<StreamingClientReceiver> {
if self.subscriptions.read().await.get(&id).is_some() {
return Err(StreamError::subscription_id_already_used(
None::<StreamError>,
));
}
let (sender, receiver) = mpsc::channel(self.config.channel_size);
self.subscriptions
.write()
.await
.insert(id.clone(), SubscriptionSender::new(id, sender));
Ok(receiver)
}
fn start_channel_task(&mut self) {
debug!("StreamingClient starting channel task");
let mut clone = self.clone();
tokio::task::spawn(async move { while clone.handle_next_message().await.is_ok() {} });
}
async fn clear_subscription(&self, id: &Id) -> bool {
debug!("StreamingClient clearing subscription: {:?}", &id);
self.subscriptions.write().await.remove(id).is_some()
}
async fn get_and_register_id(&self) -> StreamResult<SubscriptionStream> {
let id = self.client.read().await.get_next_id();
let receiver = self.register_subscription(id.clone()).await?;
Ok(SubscriptionStream::new(id, receiver, self.clone()))
}
}