Compute Fabric
Every effect in id_effect passes through Compute Fabric at the runtime boundary. You write lazy Effect descriptions and declare a ResourcePolicy; Fabric decides where, when, and how concurrently work runs.
Declarative policy
#![allow(unused)] fn main() { use id_effect::compute::{ResourcePolicy, MetricMode, MetricPolicy, RebalanceStrategy}; let policy = ResourcePolicy { memory: MetricPolicy::new(MetricMode::Max { ceiling: 0.85 }), cpu: MetricPolicy::new(MetricMode::Max { ceiling: 1.0 }), rebalance: RebalanceStrategy::ThrottleAdmission, }; }
Memory capped at 85% while CPU may use all cores is:
#![allow(unused)] fn main() { let policy = ResourcePolicy::memory_cap_max_cpu(0.85); }
Unlimited memory with even CPU spread across workers:
#![allow(unused)] fn main() { let policy = ResourcePolicy::unlimited_memory_cpu_spread(0.25); }
Supervisor loop
ComputeSupervisor polls TelemetryEngine (live sysinfo on host; mock in tests) and compares readings to policy:
- Monitor —
TelemetrySnapshot { cpu_pct, mem_pct } - Admit —
AdmissionControlleradjusts permit count - Place — fibers on
FiberPool, collections via Rayon, I/O via Tokio - Rebalance — throttle, shed, scale-out, or scale-in
Each tick emits a ComputeEvent (SupervisorTick) for observability alongside FiberEvent.
Example: memory at 61% with an 85% ceiling → headroom → admit many fibers. Memory at 86% → throttle admission.
Installing Fabric
#![allow(unused)] fn main() { use id_effect::{ThreadSleepRuntime, run_fork, succeed}; use id_effect::compute::ComputeFabric; let fabric = ComputeFabric::memory_cap_max_cpu(0.85); let rt = ThreadSleepRuntime::with_fabric(fabric); // fibers spawned via rt use the shared pool + admission }
run_with installs a default fabric for the duration of each application run. run_blocking refreshes the thread-local AdaptiveContext on every entry.
See example 120_compute_fabric_memory_cap.rs.
Adaptive parallelism
AdaptiveContext holds the current admission budget, Rayon thread count, and auto-parallel element threshold. Bulk dispatch via parallel_if_profitable consults should_parallelize_current(len) instead of a fixed 1024 default when Fabric is installed.
configure_rayon_threads— supervisor-sized Rayon poolCpuSpreadBucket— token bucket forMetricMode::Spread- Example:
121_compute_fabric_cpu_spread.rs
See also Implicit Parallelism for the full 0.4.0 API surface (map / map_effect + *_serial).
Effect binds
Independent ~ steps in an effect! block may run concurrently when the Effect Dependency Graph finds no data or capability conflict. Dependent steps stay ordered. Use #[effect(serial)] to opt out.
Example: 122_compute_fabric_effect_parallel.rs (Stream::map_effect under admission budget).
Streams and collections
- Element threshold and Rayon pool size follow
AdaptiveContext Stream::map_effectcaps concurrent effect mappers from the admission budget- Pure chunk ops (
map,filter, …) use the same Fabric snapshot viaparallel_if_profitable - CPU spread mode caps per-worker share via
CpuSpreadBucket
Cluster
When local Fabric saturates, RebalanceStrategy::ScaleOut builds a FabricJobSpec via ComputeSupervisor::scale_out. Convert with id_effect_jobs::JobSpec::from_fabric and enqueue on a FabricJobRunner. Durable cross-node steps hook through DistributedStepJournal (stub).
Example: 123_compute_fabric_cluster.rs (two-node offload with MemoryJobRunner).
ClusterResourcePolicy combines global and per-node caps with PlacementMode (LocalFirst, Spread, Affinity).
Further reading
- ADR 0007
- ADR 0008 — Fabric-only parallelism (0.4.0)
- Implicit Parallelism
- Concurrency
- Scheduling — temporal vs compute scheduling