Skip to content

Remove future block_on to update to jsonrpsee v17+ #692

@haerdib

Description

@haerdib

While trying to update the jsonrpsee client to a newer version (#671), the client (most likely) deadlocked itself after 59 requests (see https://github.com/scs/substrate-api-client/actions/runs/7126321622/job/19404332411). This is because we are using future::executor::block_on within tokio runtime to make async code synchronous. More info: paritytech/jsonrpsee#1259

To ensure this deadlock does not happen, I currently see two options:

  1. Replace futures with a tokio block.
    Example replacement:
    Old code:
impl Request for JsonrpseeClient {
	fn request<R: DeserializeOwned>(&self, method: &str, params: RpcParams) -> Result<R> {
		block_on(self.inner.request(method, RpcParamsWrapper(params)))
			.map_err(|e| Error::Client(Box::new(e)))
	}
}

New code (didn't find a better way yet, should be improved):

impl Request for JsonrpseeClient {
	fn request<R: DeserializeOwned>(&self, method: &str, params: RpcParams) -> Result<R> {
                // Catch the current runtime.
		let handle = match Handle::try_current() {
			Ok(handle) => handle,
			Err(_) => {
				// We are not inside a tokio runtime, so lets start one.
				let rt =
					tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
				rt.handle().clone()
			},
		};

		let client = self.inner.clone();
		let method_string = method.to_string();

		// The inner jsonrpsee client must not deserialize to the `R` value, because the return value must
		// implement `Send`. But we do not want to enforce the `R` value to implement this solely because we need
		// to de-async something. Therefore, the deserialization must happen outside of the newly spawned thread.
		// We need to spawn a new thread because tokio does not allow the blocking of the main thread:
		// ERROR: Cannot block the current thread from within a runtime.
		// This happens because a function attempted to block the current thread while the thread is being used to drive asynchronous tasks.
		let string_answer: Value = std::thread::spawn(move || {
			handle.block_on(client.request(&method_string, RpcParamsWrapper(params)))
		})
		.join()
		.unwrap()
		.map_err(|e| Error::Client(Box::new(e)))?;

		let deserialized_value: R = serde_json::from_value(string_answer)?;
		Ok(deserialized_value)
	}
}
  1. Open PR on jsonrpsee providing sync api

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions