use std::num::{NonZero, NonZeroUsize};
use many_cpus::ProcessorSet;
use crate::runtime::config::BlockingPoolPolicy;
use crate::runtime::error::Error;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct WorkersPolicy(WorkersPolicyKind);
impl WorkersPolicy {
#[must_use]
pub const fn exactly(count: usize) -> Self {
Self(WorkersPolicyKind::Exactly(count))
}
#[must_use]
pub const fn at_most(count: usize) -> Self {
Self(WorkersPolicyKind::AtMost(count))
}
#[must_use]
pub const fn all() -> Self {
Self(WorkersPolicyKind::All)
}
pub(crate) fn select(&self, available: &ProcessorSet) -> Result<ProcessorSet, Error> {
let count = match self.0 {
WorkersPolicyKind::Exactly(0) | WorkersPolicyKind::AtMost(0) => {
return Err(Error::invalid_worker_count());
}
WorkersPolicyKind::Exactly(count) if count > available.len() => {
return Err(Error::insufficient_processors(count, available.len()));
}
WorkersPolicyKind::Exactly(count) | WorkersPolicyKind::AtMost(count) => count.min(available.len()),
WorkersPolicyKind::Auto | WorkersPolicyKind::All => {
return Ok(available.to_builder().take_all().expect("available processor sets are nonempty"));
}
};
let count = NonZero::new(count).expect("zero counts were rejected above and available processor sets are nonempty");
Ok(available
.to_builder()
.take(count)
.expect("the requested count fits in the available processor set"))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
enum WorkersPolicyKind {
#[default]
Auto,
Exactly(usize),
AtMost(usize),
All,
}
#[derive(Debug, PartialEq)]
pub(crate) struct RuntimeConfig {
pub(crate) workers_policy: WorkersPolicy,
pub(crate) stack_size: NonZeroUsize,
pub(crate) blocking_pool_policy: BlockingPoolPolicy,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
workers_policy: WorkersPolicy::default(),
stack_size: NonZeroUsize::new(2 * 1024 * 1024).expect("the default stack size is nonzero"),
blocking_pool_policy: BlockingPoolPolicy::default(),
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))] mod tests {
#[cfg(not(miri))]
use many_cpus::SystemHardware;
use super::*;
#[test]
fn default_runtime_shares_blocking_pool() {
assert_eq!(RuntimeConfig::default().blocking_pool_policy, BlockingPoolPolicy::shared());
}
#[cfg(not(miri))]
#[test]
fn exact_count_selects_requested_processors() {
const COUNT: WorkersPolicy = WorkersPolicy::exactly(1);
let available = SystemHardware::current().processors();
let selected = COUNT.select(&available).unwrap();
assert_eq!(selected.len(), 1);
}
#[cfg(not(miri))]
#[test]
fn exact_count_accepts_all_available_processors() {
let available = SystemHardware::current().processors().take(NonZero::new(1).unwrap()).unwrap();
let selected = WorkersPolicy::exactly(available.len()).select(&available).unwrap();
assert_eq!(selected.len(), available.len());
}
#[cfg(not(miri))]
#[test]
fn unavailable_exact_count_returns_error() {
let available = SystemHardware::current().processors().take(NonZero::new(1).unwrap()).unwrap();
WorkersPolicy::exactly(2).select(&available).unwrap_err();
}
#[cfg(not(miri))]
#[test]
fn maximum_count_clamps_to_available_processors() {
let available = SystemHardware::current().processors();
let selected = WorkersPolicy::at_most(usize::MAX).select(&available).unwrap();
assert_eq!(selected.len(), available.len());
}
#[cfg(not(miri))]
#[test]
fn maximum_count_caps_processor_selection() {
const COUNT: WorkersPolicy = WorkersPolicy::at_most(1);
let available = SystemHardware::current().processors();
let selected = COUNT.select(&available).unwrap();
assert_eq!(selected.len(), 1);
}
#[cfg(not(miri))]
#[test]
fn automatic_count_uses_all_available_processors() {
let available = SystemHardware::current().processors();
assert_eq!(WorkersPolicy::default().select(&available).unwrap().len(), available.len(),);
}
#[cfg(not(miri))]
#[test]
fn all_processors_uses_all_available_processors() {
const COUNT: WorkersPolicy = WorkersPolicy::all();
let available = SystemHardware::current().processors();
assert_eq!(COUNT.select(&available).unwrap().len(), available.len());
}
}