From da6dc375d679d302e379214564913aee7ba6f722 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 28 Jun 2026 18:50:24 +0900 Subject: fix(http): avoid nested tokio runtime panic in download path The CurlDownloader owned a tokio runtime and block_on'd reqwest from its sync tick(), while the repository/installer/downloader sync bridges each created another Runtime and block_on'd async fns that reach that leaf. Driving one Runtime::block_on from within another panics with "Cannot start a runtime from within a runtime", hit by `require` when fetching p2 metadata. Switch CurlDownloader to a blocking reqwest client (its own internal thread, never nested) and replace the per-call Runtime::new().block_on bridges with a no-reactor sync_executor::block_on helper. No awaited future parks on a reactor once the only async I/O is blocking, so the helper can be nested freely. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/shirabe/src/util/sync_executor.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/shirabe/src/util/sync_executor.rs (limited to 'crates/shirabe/src/util/sync_executor.rs') diff --git a/crates/shirabe/src/util/sync_executor.rs b/crates/shirabe/src/util/sync_executor.rs new file mode 100644 index 0000000..b7512c2 --- /dev/null +++ b/crates/shirabe/src/util/sync_executor.rs @@ -0,0 +1,28 @@ +//! Minimal synchronous future executor used as a drop-in for the `tokio::runtime::Runtime::new()` +//! + `block_on` sync bridges scattered across the codebase (repository / installer / downloader). +//! +//! Those bridges drive `async fn`s whose `.await` points all resolve synchronously: the only async +//! I/O (reqwest in `CurlDownloader`) is performed through a blocking client, so no reactor is +//! required. Nesting `tokio` runtimes is forbidden ("Cannot start a runtime from within a runtime"), +//! which is why this no-reactor executor exists; it can be nested freely. +//! +//! TODO(phase-e): remove this module once the async bridges are either made genuinely synchronous or +//! consolidated onto a single shared runtime driven from `main`. + +use std::future::Future; +use std::task::{Context, Poll}; + +/// Polls `fut` to completion on the current thread without any tokio runtime. +/// +/// Relies on the invariant that no awaited future parks on a reactor (it would otherwise spin). +pub fn block_on(fut: F) -> F::Output { + let mut fut = std::pin::pin!(fut); + let waker = std::task::Waker::noop(); + let mut cx = Context::from_waker(waker); + loop { + if let Poll::Ready(value) = fut.as_mut().poll(&mut cx) { + return value; + } + std::hint::spin_loop(); + } +} -- cgit v1.3.1