use serde::{Deserialize, Serialize};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
pub const LIMITS_CONFIG_VERSION: u64 = 1;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LimitsProfile {
InteractiveV1,
}
impl LimitsProfile {
pub fn limits(self) -> Limits {
match self {
Self::InteractiveV1 => interactive_v1(),
}
}
pub fn wire_name(self) -> &'static str {
match self {
Self::InteractiveV1 => "interactive_v1",
}
}
fn from_wire_name(name: &str) -> Option<Self> {
match name {
"interactive_v1" => Some(Self::InteractiveV1),
_ => None,
}
}
}
macro_rules! limit_fields {
($( $(#[$doc:meta])* $field:ident / $with:ident = $default:expr; )+) => {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "LimitsConfig", try_from = "LimitsConfig")]
pub struct Limits {
pub(crate) profile: LimitsProfile,
$( pub(crate) $field: usize, )+
pub(crate) deadline_unix_ms: Option<u64>,
}
impl Limits {
$(
$(#[$doc])*
#[must_use]
pub fn $field(&self) -> usize {
self.$field
}
)+
$(
#[doc = concat!("Returns `self` with `", stringify!($field), "` replaced. See [`Limits::", stringify!($field), "`].")]
#[must_use]
pub fn $with(mut self, value: usize) -> Self {
self.$field = value;
self
}
)+
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct LimitsOverrides {
$(
#[serde(
default,
deserialize_with = "deserialize_override_forbidding_null",
skip_serializing_if = "Option::is_none"
)]
$field: Option<u64>,
)+
#[serde(
default,
deserialize_with = "deserialize_override_forbidding_null",
skip_serializing_if = "Option::is_none"
)]
deadline_unix_ms: Option<u64>,
}
impl LimitsOverrides {
fn diff(limits: &Limits) -> Self {
let baseline = limits.profile.limits();
Self {
$( $field: (limits.$field != baseline.$field)
.then(|| portable_count(limits.$field)), )+
deadline_unix_ms: match (limits.deadline_unix_ms, baseline.deadline_unix_ms) {
(Some(deadline), baseline) if baseline != Some(deadline) => Some(deadline),
_ => None,
},
}
}
fn apply(self, profile: LimitsProfile) -> Result<Limits, LimitsConfigError> {
let mut limits = profile.limits();
$(
if let Some(value) = self.$field {
limits.$field = override_to_usize(stringify!($field), value)?;
}
)+
if let Some(value) = self.deadline_unix_ms {
limits.deadline_unix_ms = Some(value);
}
Ok(limits)
}
}
fn interactive_v1() -> Limits {
Limits {
profile: LimitsProfile::InteractiveV1,
$( $field: $default, )+
deadline_unix_ms: None,
}
}
};
}
limit_fields! {
max_input_bytes / with_max_input_bytes = 4 * 1024 * 1024;
max_source_bytes / with_max_source_bytes = 1024 * 1024;
max_bundle_bytes / with_max_bundle_bytes = 8 * 1024 * 1024;
max_sources / with_max_sources = 256;
max_source_id_bytes / with_max_source_id_bytes = 1024;
max_import_depth / with_max_import_depth = 64;
max_import_edges / with_max_import_edges = 1024;
max_source_nesting / with_max_source_nesting = 256;
max_type_depth / with_max_type_depth = 256;
max_value_nesting / with_max_value_nesting = 64;
max_type_nodes / with_max_type_nodes = 100_000;
max_graph_edges / with_max_graph_edges = 1_000_000;
max_declarations / with_max_declarations = 100_000;
max_fields / with_max_fields = 500_000;
max_methods / with_max_methods = 100_000;
max_function_values / with_max_function_values = 500_000;
max_string_bytes / with_max_string_bytes = 1024 * 1024;
max_producer_bytes / with_max_producer_bytes = 4096;
max_diagnostics / with_max_diagnostics = 100;
max_canonicalization_work / with_max_canonicalization_work = 10_000_000;
max_provenance_work / with_max_provenance_work = 10_000_000;
max_source_identity_work / with_max_source_identity_work = 400_000_000;
max_artifact_identity_work / with_max_artifact_identity_work = 10_000_000;
max_type_preflight_work / with_max_type_preflight_work = 10_000_000;
max_value_depth / with_max_value_depth = 256;
max_value_elements / with_max_value_elements = 1_000_000;
max_value_bytes / with_max_value_bytes = 16 * 1024 * 1024;
}
impl Default for Limits {
fn default() -> Self {
LimitsProfile::InteractiveV1.limits()
}
}
impl Limits {
pub fn profile(&self) -> LimitsProfile {
self.profile
}
pub fn deadline_unix_ms(&self) -> Option<u64> {
self.deadline_unix_ms
}
#[must_use]
pub fn with_deadline_unix_ms(mut self, deadline_unix_ms: Option<u64>) -> Self {
self.deadline_unix_ms = deadline_unix_ms;
self
}
pub fn deadline_exceeded(&self) -> bool {
let Some(deadline) = self.deadline_unix_ms else {
return false;
};
#[cfg(target_os = "unknown")]
{
let _ = deadline;
true
}
#[cfg(not(target_os = "unknown"))]
{
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(u64::MAX, |duration| {
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
});
now >= deadline
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LimitsConfig {
version: u64,
profile: String,
#[serde(default)]
overrides: LimitsOverrides,
}
impl From<&Limits> for LimitsConfig {
fn from(limits: &Limits) -> Self {
Self {
version: LIMITS_CONFIG_VERSION,
profile: limits.profile.wire_name().to_string(),
overrides: LimitsOverrides::diff(limits),
}
}
}
impl From<Limits> for LimitsConfig {
fn from(limits: Limits) -> Self {
Self::from(&limits)
}
}
impl TryFrom<LimitsConfig> for Limits {
type Error = LimitsConfigError;
fn try_from(config: LimitsConfig) -> Result<Self, LimitsConfigError> {
if config.version != LIMITS_CONFIG_VERSION {
return Err(LimitsConfigError {
code: "unsupported_limits_version",
path: "$.version".to_string(),
message: format!(
"unsupported limits config version {}; this build supports version {LIMITS_CONFIG_VERSION}",
config.version
),
});
}
let profile =
LimitsProfile::from_wire_name(&config.profile).ok_or_else(|| LimitsConfigError {
code: "unsupported_limits_profile",
path: "$.profile".to_string(),
message: format!(
"unknown limits profile {:?}; known profiles: \"interactive_v1\"",
config.profile
),
})?;
config.overrides.apply(profile)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LimitsConfigError {
code: &'static str,
path: String,
message: String,
}
impl LimitsConfigError {
pub fn code(&self) -> &str {
self.code
}
pub fn path(&self) -> &str {
&self.path
}
pub fn message(&self) -> &str {
&self.message
}
}
impl std::fmt::Display for LimitsConfigError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
formatter,
"{} at {}: {}",
self.code, self.path, self.message
)
}
}
impl std::error::Error for LimitsConfigError {}
fn deserialize_override_forbidding_null<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: serde::Deserializer<'de>,
{
u64::deserialize(deserializer).map(Some)
}
pub(crate) fn portable_count(value: usize) -> u64 {
value as u64
}
fn representable(value: u64, platform_max: u64) -> bool {
value <= platform_max
}
fn override_to_usize(field: &'static str, value: u64) -> Result<usize, LimitsConfigError> {
if !representable(value, portable_count(usize::MAX)) {
return Err(LimitsConfigError {
code: "limit_override_unrepresentable",
path: format!("$.overrides.{field}"),
message: format!(
"{field} override {value} exceeds this platform's usize::MAX ({})",
usize::MAX
),
});
}
Ok(value as usize)
}
#[derive(Clone, Default)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
}
impl std::fmt::Debug for CancellationToken {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("CancellationToken")
.field("cancelled", &self.is_cancelled())
.finish()
}
}
impl PartialEq for CancellationToken {
fn eq(&self, other: &Self) -> bool {
self.is_cancelled() == other.is_cancelled()
}
}
impl Eq for CancellationToken {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeContext {
pub limits: Limits,
#[serde(skip, default)]
cancellation: CancellationToken,
}
impl PartialEq for RuntimeContext {
fn eq(&self, other: &Self) -> bool {
self.limits == other.limits
}
}
impl Eq for RuntimeContext {}
impl RuntimeContext {
pub fn new(limits: Limits) -> Self {
Self {
limits,
cancellation: CancellationToken::new(),
}
}
pub fn with_cancellation(mut self, cancellation: CancellationToken) -> Self {
self.cancellation = cancellation;
self
}
pub fn cancellation_token(&self) -> CancellationToken {
self.cancellation.clone()
}
pub(crate) fn budget(&self) -> crate::budget::Budget<'_> {
crate::budget::Budget::new(&self.limits, self.cancellation.clone())
}
}
impl Default for RuntimeContext {
fn default() -> Self {
Self::new(Limits::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn representable_simulates_the_32_bit_boundary_exactly() {
let simulated_32_bit_max = portable_count(u32::MAX as usize);
assert!(representable(u32::MAX as u64, simulated_32_bit_max));
assert!(!representable(u32::MAX as u64 + 1, simulated_32_bit_max));
assert!(!representable(u64::MAX, simulated_32_bit_max));
assert!(representable(0, simulated_32_bit_max));
}
#[test]
fn override_conversion_is_exact_or_a_structured_error() {
assert_eq!(override_to_usize("max_input_bytes", 0), Ok(0));
assert_eq!(
override_to_usize("max_input_bytes", portable_count(usize::MAX)),
Ok(usize::MAX)
);
#[cfg(target_pointer_width = "64")]
{
let error = LimitsConfigError {
code: "limit_override_unrepresentable",
path: "$.overrides.max_input_bytes".to_string(),
message: "max_input_bytes override 5000000000 exceeds this platform's usize::MAX (4294967295)".to_string(),
};
assert_eq!(error.code(), "limit_override_unrepresentable");
assert_eq!(error.path(), "$.overrides.max_input_bytes");
}
#[cfg(not(target_pointer_width = "64"))]
{
let error = override_to_usize("max_input_bytes", u64::MAX).unwrap_err();
assert_eq!(error.code(), "limit_override_unrepresentable");
assert_eq!(error.path(), "$.overrides.max_input_bytes");
}
}
#[test]
fn overrides_diff_and_apply_round_trip() {
let limits = Limits::default()
.with_max_input_bytes(1)
.with_max_diagnostics(0)
.with_deadline_unix_ms(Some(7));
let config = LimitsConfig::from(&limits);
assert_eq!(Limits::try_from(config), Ok(limits));
let untouched = Limits::default();
assert_eq!(
LimitsOverrides::diff(&untouched),
LimitsOverrides::default()
);
}
#[test]
fn an_override_equal_to_the_profile_value_is_normalized_away() {
let baseline = Limits::default();
let explicit = Limits::default().with_max_input_bytes(baseline.max_input_bytes());
assert_eq!(LimitsOverrides::diff(&explicit), LimitsOverrides::default());
assert_eq!(explicit, baseline);
}
#[test]
fn profile_wire_names_round_trip() {
assert_eq!(LimitsProfile::InteractiveV1.wire_name(), "interactive_v1");
assert_eq!(
LimitsProfile::from_wire_name("interactive_v1"),
Some(LimitsProfile::InteractiveV1)
);
assert_eq!(LimitsProfile::from_wire_name("interactive-v1"), None);
assert_eq!(LimitsProfile::from_wire_name("server_v1"), None);
}
}