Skip to content

Commit 54f4dcd

Browse files
authored
refactor(async client): refactor background task (#1145)
* refactor(async client): refactor background task Split send and receive to separate tasks to support multiplexing reads/writes * fix tests * dont rely on tokio::spawn handles anymore * fix build * fix build again * switch to std::sync::Mutex * fix tests again * bench stuff * don't block in read task * fix build * fix nits * works now * revert bench code * replace unreachable with None * Revert "replace unreachable with None" This reverts commit 49edaee. * fix nits * use dashmap instead of Arc<Mutex<RequestManager> * Revert "use dashmap instead of Arc<Mutex<RequestManager>" This reverts commit d73aeeb. * refactor select loops prio for closed futs * grumbles: save waker to wake when new items are pushed * fix build * fix build again * fix some nits
1 parent 47d93a5 commit 54f4dcd

13 files changed

Lines changed: 454 additions & 270 deletions

File tree

client/http-client/src/client.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use jsonrpsee_core::client::{
4343
use jsonrpsee_core::params::BatchRequestBuilder;
4444
use jsonrpsee_core::traits::ToRpcParams;
4545
use jsonrpsee_core::{Error, JsonRawValue, TEN_MB_SIZE_BYTES};
46-
use jsonrpsee_types::{ErrorObject, ResponseSuccess, TwoPointZero};
46+
use jsonrpsee_types::{ErrorObject, InvalidRequestId, ResponseSuccess, TwoPointZero};
4747
use serde::de::DeserializeOwned;
4848
use tower::layer::util::Identity;
4949
use tower::{Layer, Service};
@@ -320,7 +320,7 @@ where
320320
if response.id == id {
321321
Ok(result)
322322
} else {
323-
Err(Error::InvalidRequestId)
323+
Err(InvalidRequestId::NotPendingRequest(response.id.to_string()).into())
324324
}
325325
}
326326

@@ -363,7 +363,7 @@ where
363363
}
364364

365365
for rp in json_rps {
366-
let id = rp.id.try_parse_inner_as_number().ok_or(Error::InvalidRequestId)?;
366+
let id = rp.id.try_parse_inner_as_number()?;
367367

368368
let res = match ResponseSuccess::try_from(rp) {
369369
Ok(r) => {
@@ -385,7 +385,7 @@ where
385385
if let Some(elem) = maybe_elem {
386386
*elem = res;
387387
} else {
388-
return Err(Error::InvalidRequestId);
388+
return Err(InvalidRequestId::NotPendingRequest(id.to_string()).into());
389389
}
390390
}
391391

client/http-client/src/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async fn method_call_with_wrong_id_kind() {
6060
let uri = format!("http://{server_addr}");
6161
let client = HttpClientBuilder::default().id_format(IdKind::String).build(&uri).unwrap();
6262
let res: Result<String, Error> = client.request("o", rpc_params![]).with_default_timeout().await.unwrap();
63-
assert!(matches!(res, Err(Error::InvalidRequestId)));
63+
assert!(matches!(res, Err(Error::InvalidRequestId(_))));
6464
}
6565

6666
#[tokio::test]
@@ -96,7 +96,7 @@ async fn response_with_wrong_id() {
9696
.await
9797
.unwrap()
9898
.unwrap_err();
99-
assert!(matches!(err, Error::InvalidRequestId));
99+
assert!(matches!(err, Error::InvalidRequestId(_)));
100100
}
101101

102102
#[tokio::test]

client/ws-client/src/tests.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async fn method_call_with_wrong_id_kind() {
6969
WsClientBuilder::default().id_format(IdKind::String).build(&uri).with_default_timeout().await.unwrap().unwrap();
7070

7171
let err: Result<String, Error> = client.request("o", rpc_params![]).with_default_timeout().await.unwrap();
72-
assert!(matches!(err, Err(Error::RestartNeeded(e)) if e == "Invalid request ID"));
72+
assert!(matches!(err, Err(Error::RestartNeeded(e)) if e == "request ID=0 is not a pending call"));
7373
}
7474

7575
#[tokio::test]
@@ -191,6 +191,8 @@ async fn notification_handler_works() {
191191

192192
#[tokio::test]
193193
async fn notification_without_polling_doesnt_make_client_unuseable() {
194+
init_logger();
195+
194196
let server = WebSocketTestServer::with_hardcoded_notification(
195197
"127.0.0.1:0".parse().unwrap(),
196198
server_notification("test", "server originated notification".into()),

core/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ tokio = { version = "1.16", optional = true }
3535
wasm-bindgen-futures = { version = "0.4.19", optional = true }
3636
futures-timer = { version = "3", optional = true }
3737
globset = { version = "0.4", optional = true }
38-
tokio-stream = { version = "0.1", optional = true }
3938

4039
[features]
4140
default = []
@@ -55,19 +54,22 @@ client = ["futures-util/sink", "tokio/sync"]
5554
async-client = [
5655
"async-lock",
5756
"client",
57+
"futures-util/alloc",
5858
"rustc-hash",
5959
"tokio/macros",
6060
"tokio/rt",
61-
"tokio-stream",
61+
"tokio/time",
6262
"futures-timer",
6363
]
6464
async-wasm-client = [
6565
"async-lock",
6666
"client",
67+
"futures-util/alloc",
6768
"wasm-bindgen-futures",
6869
"rustc-hash/std",
6970
"futures-timer/wasm-bindgen",
70-
"tokio-stream",
71+
"tokio/macros",
72+
"tokio/time",
7173
]
7274

7375
[dev-dependencies]

core/src/client/async_client/helpers.rs

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ use tokio::sync::{mpsc, oneshot};
3636

3737
use jsonrpsee_types::response::SubscriptionError;
3838
use jsonrpsee_types::{
39-
ErrorObject, Id, Notification, RequestSer, Response, ResponseSuccess, SubscriptionId, SubscriptionResponse,
39+
ErrorObject, Id, InvalidRequestId, Notification, RequestSer, Response, ResponseSuccess, SubscriptionId,
40+
SubscriptionResponse,
4041
};
4142
use serde_json::Value as JsonValue;
4243
use std::ops::Range;
@@ -63,7 +64,7 @@ pub(crate) fn process_batch_response(
6364
Some(state) => state,
6465
None => {
6566
tracing::warn!("Received unknown batch response");
66-
return Err(Error::InvalidRequestId);
67+
return Err(InvalidRequestId::NotPendingRequest(format!("{:?}", range)).into());
6768
}
6869
};
6970

@@ -79,7 +80,7 @@ pub(crate) fn process_batch_response(
7980
if let Some(elem) = maybe_elem {
8081
*elem = rp.result;
8182
} else {
82-
return Err(Error::InvalidRequestId);
83+
return Err(InvalidRequestId::NotPendingRequest(rp.id.to_string()).into());
8384
}
8485
}
8586

@@ -95,7 +96,7 @@ pub(crate) fn process_batch_response(
9596
pub(crate) fn process_subscription_response(
9697
manager: &mut RequestManager,
9798
response: SubscriptionResponse<JsonValue>,
98-
) -> Result<(), Option<RequestMessage>> {
99+
) -> Result<(), Option<SubscriptionId<'static>>> {
99100
let sub_id = response.params.subscription.into_owned();
100101
let request_id = match manager.get_request_id_by_subscription_id(&sub_id) {
101102
Some(request_id) => request_id,
@@ -110,9 +111,7 @@ pub(crate) fn process_subscription_response(
110111
Ok(()) => Ok(()),
111112
Err(err) => {
112113
tracing::error!("Dropping subscription {:?} error: {:?}", sub_id, err);
113-
let msg = build_unsubscribe_message(manager, request_id, sub_id)
114-
.expect("request ID and subscription ID valid checked above; qed");
115-
Err(Some(msg))
114+
Err(Some(sub_id))
116115
}
117116
},
118117
None => {
@@ -124,42 +123,42 @@ pub(crate) fn process_subscription_response(
124123

125124
/// Attempts to close a subscription when a [`SubscriptionError`] is received.
126125
///
127-
/// Returns `Ok(())` if the subscription was removed
128-
/// Return `Err(e)` if the subscription was not found.
126+
/// If the notification is not found it's just logged as a warning and the connection
127+
/// will continue.
128+
///
129+
/// It's possible that the user closed down the subscription before the actual close response is received
129130
pub(crate) fn process_subscription_close_response(
130131
manager: &mut RequestManager,
131132
response: SubscriptionError<JsonValue>,
132-
) -> Result<(), Error> {
133+
) {
133134
let sub_id = response.params.subscription.into_owned();
134-
let request_id = match manager.get_request_id_by_subscription_id(&sub_id) {
135-
Some(request_id) => request_id,
135+
match manager.get_request_id_by_subscription_id(&sub_id) {
136+
Some(request_id) => {
137+
manager.remove_subscription(request_id, sub_id).expect("Both request ID and sub ID in RequestManager; qed");
138+
}
136139
None => {
137-
tracing::error!("The server tried to close an invalid subscription: {:?}", sub_id);
138-
return Err(Error::InvalidSubscriptionId);
140+
tracing::debug!("The server tried to close an non-pending subscription: {:?}", sub_id);
139141
}
140-
};
141-
142-
manager.remove_subscription(request_id, sub_id).expect("Both request ID and sub ID in RequestManager; qed");
143-
Ok(())
142+
}
144143
}
145144

146145
/// Attempts to process an incoming notification
147146
///
148-
/// Returns Ok() if the response was successfully handled
149-
/// Returns Err() if there was no handler for the method
150-
pub(crate) fn process_notification(manager: &mut RequestManager, notif: Notification<JsonValue>) -> Result<(), Error> {
147+
/// If the notification is not found it's just logged as a warning and the connection
148+
/// will continue.
149+
///
150+
/// It's possible that user close down the subscription before this notification is received.
151+
pub(crate) fn process_notification(manager: &mut RequestManager, notif: Notification<JsonValue>) {
151152
match manager.as_notification_handler_mut(notif.method.to_string()) {
152153
Some(send_back_sink) => match send_back_sink.try_send(notif.params) {
153-
Ok(()) => Ok(()),
154+
Ok(()) => (),
154155
Err(err) => {
155-
tracing::error!("Error sending notification, dropping handler for {:?} error: {:?}", notif.method, err);
156+
tracing::warn!("Could not send notification, dropping handler for {:?} error: {:?}", notif.method, err);
156157
let _ = manager.remove_notification_handler(notif.method.into_owned());
157-
Err(Error::Custom(err.to_string()))
158158
}
159159
},
160160
None => {
161-
tracing::error!("Notification: {:?} not a registered method", notif.method);
162-
Err(Error::UnregisteredNotification(notif.method.into_owned()))
161+
tracing::debug!("Notification: {:?} not a registered method", notif.method);
163162
}
164163
}
165164
}
@@ -179,18 +178,19 @@ pub(crate) fn process_single_response(
179178

180179
match manager.request_status(&response_id) {
181180
RequestStatus::PendingMethodCall => {
182-
let send_back_oneshot = match manager.complete_pending_call(response_id) {
181+
let send_back_oneshot = match manager.complete_pending_call(response_id.clone()) {
183182
Some(Some(send)) => send,
184183
Some(None) => return Ok(None),
185-
None => return Err(Error::InvalidRequestId),
184+
None => return Err(InvalidRequestId::NotPendingRequest(response_id.to_string()).into()),
186185
};
187186

188187
let _ = send_back_oneshot.send(result);
189188
Ok(None)
190189
}
191190
RequestStatus::PendingSubscription => {
192-
let (unsub_id, send_back_oneshot, unsubscribe_method) =
193-
manager.complete_pending_subscription(response_id.clone()).ok_or(Error::InvalidRequestId)?;
191+
let (unsub_id, send_back_oneshot, unsubscribe_method) = manager
192+
.complete_pending_subscription(response_id.clone())
193+
.ok_or(InvalidRequestId::NotPendingRequest(response_id.to_string()))?;
194194

195195
let sub_id = result.map(|r| SubscriptionId::try_from(r).ok());
196196

@@ -220,23 +220,23 @@ pub(crate) fn process_single_response(
220220
Ok(None)
221221
}
222222
}
223-
RequestStatus::Subscription | RequestStatus::Invalid => Err(Error::InvalidRequestId),
223+
224+
RequestStatus::Subscription | RequestStatus::Invalid => {
225+
Err(InvalidRequestId::NotPendingRequest(response_id.to_string()).into())
226+
}
224227
}
225228
}
226229

227230
/// Sends an unsubscribe to request to server to indicate
228231
/// that the client is not interested in the subscription anymore.
229232
//
230233
// NOTE: we don't count this a concurrent request as it's part of a subscription.
231-
pub(crate) async fn stop_subscription(
232-
sender: &mut impl TransportSenderT,
233-
manager: &mut RequestManager,
234+
pub(crate) async fn stop_subscription<S: TransportSenderT>(
235+
sender: &mut S,
234236
unsub: RequestMessage,
235-
) {
236-
if let Err(e) = sender.send(unsub.raw).await {
237-
tracing::error!("Send unsubscribe request failed: {:?}", e);
238-
let _ = manager.complete_pending_call(unsub.id);
239-
}
237+
) -> Result<(), S::Error> {
238+
sender.send(unsub.raw).await?;
239+
Ok(())
240240
}
241241

242242
/// Builds an unsubscription message.
@@ -245,7 +245,7 @@ pub(crate) fn build_unsubscribe_message(
245245
sub_req_id: Id<'static>,
246246
sub_id: SubscriptionId<'static>,
247247
) -> Option<RequestMessage> {
248-
let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?;
248+
let (unsub_req_id, _, unsub, sub_id) = manager.unsubscribe(sub_req_id, sub_id)?;
249249

250250
let mut params = ArrayParams::new();
251251
params.insert(sub_id).ok()?;

core/src/client/async_client/manager.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub(crate) struct RequestManager {
9595

9696
impl RequestManager {
9797
/// Create a new `RequestManager`.
98+
#[allow(unused)]
9899
pub(crate) fn new() -> Self {
99100
Self::default()
100101
}
@@ -250,9 +251,9 @@ impl RequestManager {
250251
}
251252
}
252253

253-
/// Tries to remove a subscription.
254+
/// Removes the subscription without waiting for the unsubscribe call.
254255
///
255-
/// Returns `Some` if the subscription was removed otherwise `None`.
256+
/// Returns `Some` if the subscription was removed.
256257
pub(crate) fn remove_subscription(
257258
&mut self,
258259
request_id: RequestId,
@@ -262,6 +263,7 @@ impl RequestManager {
262263
(Entry::Occupied(request), Entry::Occupied(subscription))
263264
if matches!(request.get(), Kind::Subscription(_)) =>
264265
{
266+
// Mark the request ID as pending unsubscription.
265267
let (_req_id, kind) = request.remove_entry();
266268
let (sub_id, _req_id) = subscription.remove_entry();
267269
if let Kind::Subscription((unsub_req_id, send_back, unsub)) = kind {
@@ -274,6 +276,33 @@ impl RequestManager {
274276
}
275277
}
276278

279+
/// Initiates an unsubscribe which is not completed until the unsubscribe call
280+
/// has been acknowledged.
281+
///
282+
/// Returns `Some` if the subscription was unsubscribed.
283+
pub(crate) fn unsubscribe(
284+
&mut self,
285+
request_id: RequestId,
286+
subscription_id: SubscriptionId<'static>,
287+
) -> Option<(RequestId, SubscriptionSink, UnsubscribeMethod, SubscriptionId)> {
288+
match (self.requests.entry(request_id), self.subscriptions.entry(subscription_id)) {
289+
(Entry::Occupied(mut request), Entry::Occupied(subscription))
290+
if matches!(request.get(), Kind::Subscription(_)) =>
291+
{
292+
// Mark the request ID as "pending unsubscription" which will be resolved once the
293+
// unsubscribe call has been acknowledged.
294+
let kind = std::mem::replace(request.get_mut(), Kind::PendingMethodCall(None));
295+
let (sub_id, _req_id) = subscription.remove_entry();
296+
if let Kind::Subscription((unsub_req_id, send_back, unsub)) = kind {
297+
Some((unsub_req_id, send_back, unsub, sub_id))
298+
} else {
299+
unreachable!("Subscription is Subscription checked above; qed");
300+
}
301+
}
302+
_ => None,
303+
}
304+
}
305+
277306
/// Returns the status of a request ID
278307
pub(crate) fn request_status(&mut self, id: &RequestId) -> RequestStatus {
279308
self.requests.get(id).map_or(RequestStatus::Invalid, |kind| match kind {
@@ -473,5 +502,8 @@ mod tests {
473502
assert!(manager.complete_pending_subscription(Id::Number(3)).is_none());
474503
assert!(manager.remove_subscription(Id::Number(3), SubscriptionId::Num(1)).is_none());
475504
assert!(manager.remove_subscription(Id::Number(3), SubscriptionId::Num(0)).is_some());
505+
506+
assert!(manager.requests.is_empty());
507+
assert!(manager.subscriptions.is_empty());
476508
}
477509
}

0 commit comments

Comments
 (0)