From f18e257dcbf01e64f242249046a373df737fdae6 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Mon, 14 Sep 2026 11:31:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(distribute):=20tokio=20behind=20an=20option?= =?UTF-8?q?al=20feature,=20used=20unconditionally=20=E2=80=94=2043=20error?= =?UTF-8?q?s=20under=20--no-default-features?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo check --workspace --no-default-features error[E0433]: failed to resolve: use of unresolved module or unlinked crate `tokio` ... 43 errors, every one in aprender-distribute Found while measuring what APR-RELEASE-001 T-1's `ci / deep` would actually cost. T-1 names `--no-default-features` as one of its clauses, and that clause has never run in CI: `grep -rc no-default-features .github/workflows/*.yml` finds ONE hit and it is a Windows BUILD flag in nightly.yml. #3176 is the standing ticket. `default = ["cpu"]` and `cpu = ["tokio", "num_cpus"]`, so --no-default-features drops tokio while the source still names it in four files. TOKIO, NOT `cpu`, AND THAT IS A DELIBERATE DEPARTURE FROM THE TICKET. #3176 says "the fix is `#[cfg(feature = "cpu")]` on the affected modules ... not adding tokio as a hard dependency". The second half is right and is followed. The first half is not quite: `cpu` is only one of SIX features that pull tokio in — `cpu`, `remote`, `checkpoint`, `tui`, `serverless`, `microvm`. Gating on `cpu` would leave `--no-default-features --features remote` with tokio PRESENT and the modules that need it compiled OUT: one broken configuration traded for another. Cargo creates an implicit `tokio` feature for an optional dependency, and it is true exactly when some feature brought tokio in. That is the predicate the code actually depends on, so that is the gate. What is gated, and why each: executor::cpu the ONLY executor module that was not already feature-gated, and the one needing tokio::process::Command / AsyncReadExt / timeout. Its five siblings (gpu, microvm, remote, tls, simd) are all gated; this makes it consistent rather than special. messaging tokio throughout scheduler tokio throughout Pool, PoolBuilder and their impls, and the imports they need the crate's high-level API is async to its core -- tokio::sync::RwLock, tokio::task::JoinSet, tokio::time::sleep, and a CpuExecutor that spawns processes through tokio. There is no meaningful tokio-free Pool to preserve, so the whole surface is gated with the runtime it requires rather than half-gated into something that compiles and cannot run. Verified in every direction, because a gate that breaks the normal path is worse than the defect: cargo check -p aprender-distribute --no-default-features 0 errors (was 43) cargo check -p aprender-distribute 0 errors cargo check -p aprender-distribute --all-features 0 errors cargo test -p aprender-distribute --lib 84 passed clippy -D warnings, all three feature sets 0 errors cargo check --workspace --no-default-features 0 errors <- the T-1 clause Load-bearing, proved by mutation rather than asserted: removing the gate on `executor::cpu` alone puts --no-default-features back to 10 errors; restoring it returns 0. Closes #3176 Pmat-Ticket: PMAT-1098 Co-Authored-By: Claude Opus 5 (1M context) --- .../aprender-distribute/src/executor/mod.rs | 4 +++ crates/aprender-distribute/src/lib.rs | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/crates/aprender-distribute/src/executor/mod.rs b/crates/aprender-distribute/src/executor/mod.rs index 77903d7204..e1388946a7 100644 --- a/crates/aprender-distribute/src/executor/mod.rs +++ b/crates/aprender-distribute/src/executor/mod.rs @@ -3,6 +3,10 @@ //! This module defines the `Executor` trait and provides implementations //! for different backends (CPU, GPU, Remote). +// `cpu` is the only executor module that was NOT feature-gated, and it is the +// one that needs tokio (`tokio::process::Command`, `AsyncReadExt`, `timeout`). +// Its five siblings below are all gated; this makes it consistent. #3176. +#[cfg(feature = "tokio")] pub mod cpu; #[cfg(feature = "gpu")] diff --git a/crates/aprender-distribute/src/lib.rs b/crates/aprender-distribute/src/lib.rs index 283501abeb..014794d9fe 100644 --- a/crates/aprender-distribute/src/lib.rs +++ b/crates/aprender-distribute/src/lib.rs @@ -81,7 +81,19 @@ mod generated_contracts; pub mod checkpoint; pub mod error; pub mod executor; +// TOKIO, NOT `cpu`. #3176 proposed `#[cfg(feature = "cpu")]` and the intent is +// right — gate the modules, do NOT make tokio a hard dependency — but `cpu` is +// only one of SIX features that pull tokio in: `cpu`, `remote`, `checkpoint`, +// `tui`, `serverless`, `microvm`. Gating on `cpu` would leave +// `--no-default-features --features remote` with tokio present and the modules +// that need it compiled out, which trades one broken configuration for another. +// +// Cargo creates an implicit `tokio` feature for the optional dependency, and it +// is true exactly when some feature brought tokio in. That is the predicate the +// code actually depends on. +#[cfg(feature = "tokio")] pub mod messaging; +#[cfg(feature = "tokio")] pub mod scheduler; #[cfg(feature = "serverless")] pub mod serverless; @@ -90,16 +102,33 @@ pub mod task; pub mod tensor; // tui module removed — was ratatui-only +// Only the Pool API uses these, and the Pool API is tokio-gated below. +#[cfg(feature = "tokio")] use error::{RepartirError, Result}; +// `Pool` is the crate's high-level API and it is async to its core -- +// tokio::sync::RwLock, tokio::task::JoinSet, tokio::time::sleep, and a +// CpuExecutor that spawns processes through tokio. There is no meaningful +// tokio-free Pool to preserve, so the whole API surface is gated with the +// runtime it requires rather than half-gated into something that compiles and +// cannot run. #3176. +#[cfg(feature = "tokio")] use executor::cpu::CpuExecutor; +#[cfg(feature = "tokio")] use executor::Executor; +#[cfg(feature = "tokio")] use scheduler::Scheduler; +#[cfg(feature = "tokio")] use std::sync::Arc; +#[cfg(feature = "tokio")] use task::{ExecutionResult, Task}; +#[cfg(feature = "tokio")] use tokio::sync::RwLock; +#[cfg(feature = "tokio")] use tokio::task::JoinSet; +#[cfg(feature = "tokio")] use tracing::{debug, info}; +#[cfg(feature = "tokio")] /// High-level API for distributed task execution. /// /// The `Pool` manages executors, schedules tasks, and coordinates results. @@ -131,6 +160,7 @@ pub struct Pool { workers: Arc>>, } +#[cfg(feature = "tokio")] impl Pool { /// Creates a new pool builder. #[must_use] @@ -182,6 +212,7 @@ impl Pool { } } +#[cfg(feature = "tokio")] /// Builder for `Pool`. #[derive(Default)] pub struct PoolBuilder { @@ -189,6 +220,7 @@ pub struct PoolBuilder { max_queue_size: Option, } +#[cfg(feature = "tokio")] impl PoolBuilder { /// Sets the number of CPU workers. #[must_use]