From f1ae35f68097b6c6c52f80fd15364959bc30e186 Mon Sep 17 00:00:00 2001 From: elnosh Date: Thu, 27 Aug 2026 18:09:55 -0400 Subject: [PATCH] Require a multithreaded runtime when setting up the node We already note on `Node::start` that we require the outer runtime to be of the multithreaded flavor, but we never check it. We should, as our synchronous API methods enter the runtime via `block_in_place`, which panics on current-thread runtimes. Co-Authored-By: Claude Opus 5 --- src/builder.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index ab641cf86..4431cc05c 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -367,6 +367,8 @@ impl NodeBuilder { /// /// If not provided, the node will spawn its own runtime or reuse any outer runtime context it /// can detect. + /// + /// Note we require the given runtime to be of the `multithreaded` flavor. #[cfg_attr(feature = "uniffi", allow(dead_code))] pub fn set_runtime(&mut self, runtime_handle: tokio::runtime::Handle) -> &mut Self { self.runtime_handle = Some(runtime_handle); @@ -910,14 +912,24 @@ impl NodeBuilder { } fn setup_runtime(&self, logger: &Arc) -> Result, BuildError> { - if let Some(handle) = self.runtime_handle.as_ref() { - Ok(Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(logger)))) + let runtime = if let Some(handle) = self.runtime_handle.as_ref() { + Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(logger))) } else { - Ok(Arc::new(Runtime::new(Arc::clone(logger)).map_err(|e| { + Arc::new(Runtime::new(Arc::clone(logger)).map_err(|e| { log_error!(logger, "Failed to setup tokio runtime: {}", e); BuildError::RuntimeSetupFailed - })?)) + })?) + }; + + if runtime.handle().runtime_flavor() != tokio::runtime::RuntimeFlavor::MultiThread { + log_error!( + logger, + "Failed to setup tokio runtime: we require a multithreaded runtime." + ); + return Err(BuildError::RuntimeSetupFailed); } + + Ok(runtime) } fn build_with_store_and_logger( @@ -2698,4 +2710,33 @@ mod tests { let node = sanitize_alias(alias); assert_eq!(node.err().unwrap(), BuildError::InvalidNodeAlias); } + + #[test] + fn rejects_non_multithreaded_runtimes() { + let logger = Arc::new(Logger::new_log_facade()); + let current_thread_runtime = + tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + + let mut builder = NodeBuilder::new(); + builder.set_runtime(current_thread_runtime.handle().clone()); + assert_eq!( + builder.setup_runtime(&logger).err(), + Some(BuildError::RuntimeSetupFailed), + "a current-thread runtime given via `set_runtime` should be rejected" + ); + + current_thread_runtime.block_on(async { + assert_eq!( + NodeBuilder::new().setup_runtime(&logger).err(), + Some(BuildError::RuntimeSetupFailed), + "a detected outer current-thread runtime context should be rejected" + ); + }); + + let multi_thread_runtime = + tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap(); + let mut builder = NodeBuilder::new(); + builder.set_runtime(multi_thread_runtime.handle().clone()); + assert!(builder.setup_runtime(&logger).is_ok(), "a multi-threaded runtime should be used"); + } }