Propagate subscription failures to the caller
This commit is contained in:
parent
8c376f58cb
commit
9d477d45c7
|
@ -63,9 +63,9 @@ impl HttpSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct RpcErrorObject {
|
pub(crate) struct RpcErrorObject {
|
||||||
code: i64,
|
pub code: i64,
|
||||||
message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StatsUpdater<'a> {
|
struct StatsUpdater<'a> {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
http_sender::RpcErrorObject,
|
||||||
rpc_config::{
|
rpc_config::{
|
||||||
RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
|
RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
|
||||||
RpcProgramAccountsConfig, RpcSignatureSubscribeConfig, RpcTransactionLogsConfig,
|
RpcProgramAccountsConfig, RpcSignatureSubscribeConfig, RpcTransactionLogsConfig,
|
||||||
|
@ -15,6 +16,7 @@ use {
|
||||||
sink::SinkExt,
|
sink::SinkExt,
|
||||||
stream::{BoxStream, StreamExt},
|
stream::{BoxStream, StreamExt},
|
||||||
},
|
},
|
||||||
|
log::*,
|
||||||
serde::de::DeserializeOwned,
|
serde::de::DeserializeOwned,
|
||||||
serde_json::{json, Map, Value},
|
serde_json::{json, Map, Value},
|
||||||
solana_account_decoder::UiAccount,
|
solana_account_decoder::UiAccount,
|
||||||
|
@ -52,21 +54,19 @@ pub enum PubsubClientError {
|
||||||
#[error("websocket error")]
|
#[error("websocket error")]
|
||||||
WsError(#[from] tokio_tungstenite::tungstenite::Error),
|
WsError(#[from] tokio_tungstenite::tungstenite::Error),
|
||||||
|
|
||||||
#[error("connection closed")]
|
#[error("connection closed (({0})")]
|
||||||
ConnectionClosed,
|
ConnectionClosed(String),
|
||||||
|
|
||||||
#[error("json parse error")]
|
#[error("json parse error")]
|
||||||
JsonParseError(#[from] serde_json::error::Error),
|
JsonParseError(#[from] serde_json::error::Error),
|
||||||
|
|
||||||
#[error("subscribe failed: {reason}")]
|
#[error("subscribe failed: {reason}")]
|
||||||
SubscribeFailed {
|
SubscribeFailed { reason: String, message: String },
|
||||||
reason: &'static str,
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UnsubscribeFn = Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>;
|
type UnsubscribeFn = Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>;
|
||||||
type SubscribeResponseMsg = (mpsc::UnboundedReceiver<Value>, UnsubscribeFn);
|
type SubscribeResponseMsg =
|
||||||
|
Result<(mpsc::UnboundedReceiver<Value>, UnsubscribeFn), PubsubClientError>;
|
||||||
type SubscribeRequestMsg = (String, Value, oneshot::Sender<SubscribeResponseMsg>);
|
type SubscribeRequestMsg = (String, Value, oneshot::Sender<SubscribeResponseMsg>);
|
||||||
type SubscribeResult<'a, T> = PubsubClientResult<(BoxStream<'a, T>, UnsubscribeFn)>;
|
type SubscribeResult<'a, T> = PubsubClientResult<(BoxStream<'a, T>, UnsubscribeFn)>;
|
||||||
|
|
||||||
|
@ -106,10 +106,11 @@ impl PubsubClient {
|
||||||
let (response_tx, response_rx) = oneshot::channel();
|
let (response_tx, response_rx) = oneshot::channel();
|
||||||
self.subscribe_tx
|
self.subscribe_tx
|
||||||
.send((operation.to_string(), params, response_tx))
|
.send((operation.to_string(), params, response_tx))
|
||||||
.map_err(|_| PubsubClientError::ConnectionClosed)?;
|
.map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))?;
|
||||||
|
|
||||||
let (notifications, unsubscribe) = response_rx
|
let (notifications, unsubscribe) = response_rx
|
||||||
.await
|
.await
|
||||||
.map_err(|_| PubsubClientError::ConnectionClosed)?;
|
.map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))??;
|
||||||
Ok((
|
Ok((
|
||||||
UnboundedReceiverStream::new(notifications)
|
UnboundedReceiverStream::new(notifications)
|
||||||
.filter_map(|value| ready(serde_json::from_value::<T>(value).ok()))
|
.filter_map(|value| ready(serde_json::from_value::<T>(value).ok()))
|
||||||
|
@ -225,6 +226,7 @@ impl PubsubClient {
|
||||||
Some(msg) => msg?,
|
Some(msg) => msg?,
|
||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
|
trace!("ws.next(): {:?}", &msg);
|
||||||
|
|
||||||
// Get text from the message
|
// Get text from the message
|
||||||
let text = match msg {
|
let text = match msg {
|
||||||
|
@ -237,28 +239,41 @@ impl PubsubClient {
|
||||||
Message::Pong(_data) => continue,
|
Message::Pong(_data) => continue,
|
||||||
Message::Close(_frame) => break,
|
Message::Close(_frame) => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let mut json: Map<String, Value> = serde_json::from_str(&text)?;
|
let mut json: Map<String, Value> = serde_json::from_str(&text)?;
|
||||||
|
|
||||||
// Subscribe/Unsubscribe response, example:
|
// Subscribe/Unsubscribe response, example:
|
||||||
// `{"jsonrpc":"2.0","result":5308752,"id":1}`
|
// `{"jsonrpc":"2.0","result":5308752,"id":1}`
|
||||||
if let Some(id) = json.get("id") {
|
if let Some(id) = json.get("id") {
|
||||||
// Request Id
|
|
||||||
let id = id.as_u64().ok_or_else(|| {
|
let id = id.as_u64().ok_or_else(|| {
|
||||||
PubsubClientError::SubscribeFailed { reason: "invalid `id` field", message: text.clone() }
|
PubsubClientError::SubscribeFailed { reason: "invalid `id` field".into(), message: text.clone() }
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Check that response is unsubscribe
|
let err = json.get("error").map(|error_object| {
|
||||||
|
match serde_json::from_value::<RpcErrorObject>(error_object.clone()) {
|
||||||
|
Ok(rpc_error_object) => {
|
||||||
|
format!("{} ({})", rpc_error_object.message, rpc_error_object.code)
|
||||||
|
}
|
||||||
|
Err(err) => format!(
|
||||||
|
"Failed to deserialize RPC error response: {} [{}]",
|
||||||
|
serde_json::to_string(error_object).unwrap(),
|
||||||
|
err
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(response_tx) = requests_unsubscribe.remove(&id) {
|
if let Some(response_tx) = requests_unsubscribe.remove(&id) {
|
||||||
let _ = response_tx.send(()); // do not care if receiver is closed
|
let _ = response_tx.send(()); // do not care if receiver is closed
|
||||||
} else {
|
} else if let Some((operation, response_tx)) = requests_subscribe.remove(&id) {
|
||||||
|
match err {
|
||||||
|
Some(reason) => {
|
||||||
|
let _ = response_tx.send(Err(PubsubClientError::SubscribeFailed { reason, message: text.clone()}));
|
||||||
|
},
|
||||||
|
None => {
|
||||||
// Subscribe Id
|
// Subscribe Id
|
||||||
let sid = json.get("result").and_then(Value::as_u64).ok_or_else(|| {
|
let sid = json.get("result").and_then(Value::as_u64).ok_or_else(|| {
|
||||||
PubsubClientError::SubscribeFailed { reason: "invalid `result` field", message: text.clone() }
|
PubsubClientError::SubscribeFailed { reason: "invalid `result` field".into(), message: text.clone() }
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get subscribe request details
|
|
||||||
let (operation, response_tx) = requests_subscribe.remove(&id).ok_or_else(|| {
|
|
||||||
PubsubClientError::SubscribeFailed { reason: "request for received `id` not found", message: text.clone() }
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Create notifications channel and unsubscribe function
|
// Create notifications channel and unsubscribe function
|
||||||
|
@ -272,17 +287,16 @@ impl PubsubClient {
|
||||||
}
|
}
|
||||||
}.boxed());
|
}.boxed());
|
||||||
|
|
||||||
// Resolve subscribe request
|
if response_tx.send(Ok((notifications_rx, unsubscribe))).is_err() {
|
||||||
match response_tx.send((notifications_rx, unsubscribe)) {
|
break;
|
||||||
Ok(()) => {
|
}
|
||||||
subscriptions.insert(sid, notifications_tx);
|
subscriptions.insert(sid, notifications_tx);
|
||||||
}
|
}
|
||||||
Err((_notifications_rx, unsubscribe)) => {
|
|
||||||
unsubscribe();
|
|
||||||
}
|
}
|
||||||
};
|
} else {
|
||||||
|
error!("Unknown request id: {}", id);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue