use super::ValidatedPluginRegistry;
use std::num::NonZeroUsize;
pub const MAX_PLUGIN_SCHEDULER_INTENT_BATCHES: usize = 1_024;
const MIN_PLUGIN_SCHEDULER_INTENT_BATCHES: NonZeroUsize = NonZeroUsize::MIN;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PluginSchedulerPolicy {
max_intent_batches: NonZeroUsize,
}
impl PluginSchedulerPolicy {
#[must_use]
pub fn from_validated_registry(registry: &ValidatedPluginRegistry) -> Self {
let max_intent_batches = registry
.enabled_plugins()
.map(|plugin| plugin.runtime_limits().max_intent_batches())
.try_fold(0_usize, usize::checked_add)
.unwrap_or(usize::MAX)
.min(MAX_PLUGIN_SCHEDULER_INTENT_BATCHES);
let max_intent_batches = max_intent_batches.max(MIN_PLUGIN_SCHEDULER_INTENT_BATCHES.get());
Self {
max_intent_batches: NonZeroUsize::new(max_intent_batches)
.unwrap_or(MIN_PLUGIN_SCHEDULER_INTENT_BATCHES),
}
}
#[must_use]
pub const fn max_intent_batches(self) -> usize {
self.max_intent_batches.get()
}
#[must_use]
pub(crate) const fn max_intent_batches_limit(self) -> NonZeroUsize {
self.max_intent_batches
}
}
impl Default for PluginSchedulerPolicy {
fn default() -> Self {
Self {
max_intent_batches: NonZeroUsize::new(MAX_PLUGIN_SCHEDULER_INTENT_BATCHES)
.expect("default scheduler cap should be non-zero"),
}
}
}
#[cfg(test)]
mod tests {
use super::{MAX_PLUGIN_SCHEDULER_INTENT_BATCHES, PluginSchedulerPolicy};
use crate::plugin::{PluginRegistryConfig, PluginRuntimeLimits};
#[test]
fn scheduler_policy_sums_enabled_plugin_bursts() {
let registry = registry_with_limits([2, 3]);
let policy = PluginSchedulerPolicy::from_validated_registry(®istry);
assert_eq!(policy.max_intent_batches(), 5);
assert_eq!(policy.max_intent_batches_limit().get(), 5);
}
#[test]
fn scheduler_policy_ignores_disabled_plugins() {
let config = PluginRegistryConfig {
plugins: vec![
plugin_config("enabled", true, 2),
plugin_config("disabled", false, 7),
],
};
let registry = config.validate().expect("registry");
let policy = PluginSchedulerPolicy::from_validated_registry(®istry);
assert_eq!(policy.max_intent_batches(), 2);
}
#[test]
fn scheduler_policy_uses_minimum_cap_without_enabled_plugins() {
let registry = PluginRegistryConfig::default()
.validate()
.expect("registry");
let policy = PluginSchedulerPolicy::from_validated_registry(®istry);
assert_eq!(policy.max_intent_batches(), 1);
}
#[test]
fn scheduler_policy_caps_registry_bursts() {
let registry = registry_with_limits([
MAX_PLUGIN_SCHEDULER_INTENT_BATCHES as u64,
MAX_PLUGIN_SCHEDULER_INTENT_BATCHES as u64,
]);
let policy = PluginSchedulerPolicy::from_validated_registry(®istry);
assert_eq!(
policy.max_intent_batches(),
MAX_PLUGIN_SCHEDULER_INTENT_BATCHES
);
}
fn registry_with_limits<const N: usize>(
limits: [u64; N],
) -> crate::plugin::ValidatedPluginRegistry {
let plugins = limits
.into_iter()
.enumerate()
.map(|(index, max_intent_batches)| {
plugin_config(&format!("plugin-{index}"), true, max_intent_batches)
})
.collect();
PluginRegistryConfig { plugins }
.validate()
.expect("registry")
}
fn plugin_config(
identity: &str,
enabled: bool,
max_intent_batches: u64,
) -> crate::plugin::PluginConfig {
crate::plugin::PluginConfig {
identity: identity.to_owned(),
component_path: format!("plugins/{identity}.wasm").into(),
enabled,
runtime_limits: PluginRuntimeLimits {
max_intent_batches,
..PluginRuntimeLimits::default()
},
..crate::plugin::PluginConfig::default()
}
}
}