From e44085eb742cb14dff22de054ef19fe725a4e2c2 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Fri, 17 Jul 2026 18:07:43 +0900 Subject: refactor(loop): drive wait() promises concurrently via FuturesUnordered Loop::wait already had the target signature and a TODO(phase-c-promise) marker noting it drove promises serially; swap the for-loop for FuturesUnordered so all promises are polled together instead of one at a time, keeping the "remember only the first error" semantics. This adds the first real use of the futures dependency (already present in Cargo.toml/Cargo.lock from earlier prep work, now finally consumed), so those lockfile/manifest changes land in this commit. Real overlap still doesn't happen yet: each promise (HttpDownloader::add/ add_copy etc.) resolves through a blocking bridge (curl_runtime()/ sync_executor::block_on) that fully occupies the thread until it settles, so this is groundwork for once a single top-level Runtime replaces those bridges. Updated the TODO(phase-c-promise) comment to reflect that. --- crates/shirabe/src/util/loop.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'crates/shirabe/src') diff --git a/crates/shirabe/src/util/loop.rs b/crates/shirabe/src/util/loop.rs index 4efd7707..c2fa3b08 100644 --- a/crates/shirabe/src/util/loop.rs +++ b/crates/shirabe/src/util/loop.rs @@ -2,6 +2,8 @@ use crate::util::HttpDownloader; use crate::util::ProcessExecutor; +use futures::StreamExt; +use futures::stream::FuturesUnordered; use shirabe_external_packages::symfony::console::helper::ProgressBar; #[derive(Debug)] @@ -44,25 +46,24 @@ impl Loop { >, _progress: Option<&mut ProgressBar>, ) -> anyhow::Result<()> { + let mut pending: FuturesUnordered<_> = promises.into_iter().collect(); let mut uncaught: Option = None; - // TODO(phase-c-promise): the asynchronous worker classes (HttpDownloader / ProcessExecutor) - // run single-threaded for now, so the promises are consumed serially. Once the workers run - // on a multi-thread runtime these futures should be driven concurrently instead of in order. + // TODO(phase-c-promise): promises are now polled concurrently via FuturesUnordered, but + // each individual future (HttpDownloader::add/add_copy etc.) still resolves through a + // blocking bridge (curl_runtime()/sync_executor::block_on), so real I/O overlap does not + // happen yet — the bridged future fully blocks the thread until it settles before the next + // one gets polled. That only changes once a single top-level Runtime replaces those bridges. // The PHP progress bar is tied to the worker active-job count and is also deferred until then. - for promise in promises { - if let Err(e) = promise.await + while let Some(result) = pending.next().await { + if let Err(e) = result && uncaught.is_none() { uncaught = Some(e); } } - if let Some(e) = uncaught { - return Err(e); - } - - Ok(()) + uncaught.map_or(Ok(()), Err) } pub fn abort_jobs(&self) { -- cgit v1.3.1