Skip to content

Commit 6100e22

Browse files
authored
Revert to using separate tokio runtime instance for RPC (#395)
> // Comment from Solana implementation: > // sadly, some parts of our current rpc implemention block the jsonrpc's > // _socket-listening_ event loop for too long, due to (blocking) long IO or intesive CPU, > // causing no further processing of incoming requests and ultimatily innocent clients timing-out. > // So create a (shared) multi-threaded event_loop for jsonrpc and set its .threads() to 1, > // so that we avoid the single-threaded event loops from being created automatically by > // jsonrpc for threads when .threads(N > 1) is given. After switching to same runtime instance we encountered an issue mentioned in original implementation. See original convo for more details [here](https://magicblock-labs.slack.com/archives/C07QF4P5HJ8/p1750067365761009) <!-- greptile_comment --> ## Greptile Summary Reverts RPC service to use a dedicated tokio runtime instance to prevent request timeouts caused by blocking operations in the JSON-RPC socket listening event loop. - Modified `magicblock-rpc/src/json_rpc_service.rs` to add dedicated runtime field and initialization function - Configured server threads to 1 to avoid automatic creation of single-threaded event loops - Increased sleep duration in `magicblock-ledger/tests/test_ledger_truncator.rs` from 1s to 3s for test stability - Change follows Solana's original implementation pattern to handle long IO/CPU operations blocking RPC requests <!-- /greptile_comment -->
1 parent b3edeb1 commit 6100e22

File tree

2 files changed

+40
-11
lines changed

2 files changed

+40
-11
lines changed

magicblock-ledger/tests/test_ledger_truncator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ async fn test_truncator_with_tx_spammer() {
228228
));
229229

230230
// Sleep some time
231-
tokio::time::sleep(Duration::from_secs(1)).await;
231+
tokio::time::sleep(Duration::from_secs(3)).await;
232232

233233
let signatures_result = handle.await;
234234
assert!(signatures_result.is_ok());

magicblock-rpc/src/json_rpc_service.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use magicblock_bank::bank::Bank;
1616
use magicblock_ledger::Ledger;
1717
use solana_perf::thread::renice_this_thread;
1818
use solana_sdk::{hash::Hash, signature::Keypair};
19+
use tokio::runtime::Runtime;
1920

2021
use crate::{
2122
handlers::{
@@ -35,6 +36,7 @@ use crate::{
3536
pub struct JsonRpcService {
3637
rpc_addr: SocketAddr,
3738
rpc_niceness_adj: i8,
39+
runtime: Arc<Runtime>,
3840
request_processor: JsonRpcRequestProcessor,
3941
startup_verification_complete: Arc<AtomicBool>,
4042
max_request_body_size: usize,
@@ -59,6 +61,7 @@ impl JsonRpcService {
5961
.max_request_body_size
6062
.unwrap_or(MAX_REQUEST_BODY_SIZE);
6163

64+
let runtime = get_runtime(&config);
6265
let rpc_niceness_adj = config.rpc_niceness_adj;
6366

6467
let startup_verification_complete =
@@ -79,6 +82,7 @@ impl JsonRpcService {
7982
rpc_addr,
8083
rpc_niceness_adj,
8184
max_request_body_size,
85+
runtime,
8286
request_processor,
8387
startup_verification_complete,
8488
rpc_thread_handle: Default::default(),
@@ -96,10 +100,10 @@ impl JsonRpcService {
96100
self.startup_verification_complete.clone();
97101
let request_processor = self.request_processor.clone();
98102
let rpc_addr = self.rpc_addr;
103+
let runtime = self.runtime.handle().clone();
99104
let max_request_body_size = self.max_request_body_size;
100105

101106
let close_handle_rc = self.close_handle.clone();
102-
let handle = tokio::runtime::Handle::current();
103107
let thread_handle = thread::Builder::new()
104108
.name("solJsonRpcSvc".to_string())
105109
.spawn(move || {
@@ -119,17 +123,18 @@ impl JsonRpcService {
119123
let server = ServerBuilder::with_meta_extractor(
120124
io,
121125
move |_req: &hyper::Request<hyper::Body>| {
122-
request_processor.clone()
126+
request_processor.clone()
123127
},
124128
)
125-
.event_loop_executor(handle)
126-
.cors(DomainsValidation::AllowOnly(vec![
127-
AccessControlAllowOrigin::Any,
128-
]))
129-
.cors_max_age(86400)
130-
.request_middleware(request_middleware)
131-
.max_request_body_size(max_request_body_size)
132-
.start_http(&rpc_addr);
129+
.event_loop_executor(runtime)
130+
.threads(1)
131+
.cors(DomainsValidation::AllowOnly(vec![
132+
AccessControlAllowOrigin::Any,
133+
]))
134+
.cors_max_age(86400)
135+
.request_middleware(request_middleware)
136+
.max_request_body_size(max_request_body_size)
137+
.start_http(&rpc_addr);
133138

134139

135140
match server {
@@ -181,3 +186,27 @@ impl JsonRpcService {
181186
&self.rpc_addr
182187
}
183188
}
189+
190+
fn get_runtime(config: &JsonRpcConfig) -> Arc<tokio::runtime::Runtime> {
191+
let rpc_threads = 1.max(config.rpc_threads);
192+
let rpc_niceness_adj = config.rpc_niceness_adj;
193+
194+
// Comment from Solana implementation:
195+
// sadly, some parts of our current rpc implemention block the jsonrpc's
196+
// _socket-listening_ event loop for too long, due to (blocking) long IO or intesive CPU,
197+
// causing no further processing of incoming requests and ultimatily innocent clients timing-out.
198+
// So create a (shared) multi-threaded event_loop for jsonrpc and set its .threads() to 1,
199+
// so that we avoid the single-threaded event loops from being created automatically by
200+
// jsonrpc for threads when .threads(N > 1) is given.
201+
Arc::new(
202+
tokio::runtime::Builder::new_multi_thread()
203+
.worker_threads(rpc_threads)
204+
.on_thread_start(move || {
205+
renice_this_thread(rpc_niceness_adj).unwrap()
206+
})
207+
.thread_name("solRpcEl")
208+
.enable_all()
209+
.build()
210+
.expect("Runtime"),
211+
)
212+
}

0 commit comments

Comments
 (0)