yggdrasil/infra/
runner_config.rs1use serde::{Deserialize, Serialize};
2#[cfg(feature = "server")]
3use std::env;
4#[cfg(feature = "server")]
5use std::sync::LazyLock;
6
7#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
8pub struct ResourceLimits {
9 pub cpu_cores: f64,
10 pub memory_mb: u64,
11 pub timeout_secs: u64,
12 pub output_bytes: u64,
13 pub allow_network: bool,
14}
15
16#[cfg(feature = "server")]
17pub struct RunnerConfig {
18 pub max_cpu_cores: f64,
19 pub max_memory_mb: u64,
20 pub max_timeout_secs: u64,
21 pub max_output_bytes: u64,
22 pub max_source_bytes: u64,
23 pub allow_network: bool,
24 pub max_concurrent: usize,
25 pub queue_timeout_secs: u64,
26 pub task_ttl_secs: u64,
27 pub docker_socket_path: String,
28 pub languages: Option<Vec<String>>,
31}
32
33#[cfg(feature = "server")]
34pub static RUNNER_CONFIG: LazyLock<RunnerConfig> = LazyLock::new(|| {
35 let cfg = crate::config::runner();
36 RunnerConfig {
37 max_cpu_cores: cfg.max_cpu_cores,
38 max_memory_mb: cfg.max_memory_mb as u64,
39 max_timeout_secs: cfg.max_timeout_secs as u64,
40 max_output_bytes: cfg.max_output_bytes,
41 max_source_bytes: cfg.max_source_bytes,
42 allow_network: cfg.allow_network,
43 max_concurrent: cfg.max_concurrent as usize,
44 queue_timeout_secs: cfg.queue_timeout_secs as u64,
45 task_ttl_secs: cfg.task_ttl_secs as u64,
46 docker_socket_path: env::var("DOCKER_SOCKET_PATH")
47 .unwrap_or_else(|_| "/var/run/docker.sock".to_string()),
48 languages: cfg.languages.map(|s| {
50 s.split(',')
51 .map(|t| t.trim().to_lowercase())
52 .filter(|t| !t.is_empty())
53 .collect()
54 }),
55 }
56});
57
58#[cfg(feature = "server")]
59pub fn clamp_limits(merged: ResourceLimits, lang_allows_network: bool) -> ResourceLimits {
60 clamp_limits_impl(merged, lang_allows_network, &RUNNER_CONFIG)
61}
62
63#[cfg(feature = "server")]
64fn clamp_limits_impl(
65 merged: ResourceLimits,
66 lang_allows_network: bool,
67 config: &RunnerConfig,
68) -> ResourceLimits {
69 let max_cpu = if config.max_cpu_cores.is_nan() {
70 2.0
71 } else {
72 config.max_cpu_cores
73 };
74 let min_cpu = 0.1f64.min(max_cpu);
75 let cpu_cores = if merged.cpu_cores.is_nan() {
76 min_cpu
77 } else {
78 merged.cpu_cores.clamp(min_cpu, max_cpu)
79 };
80
81 let min_mem = 16.min(config.max_memory_mb);
82 let memory_mb = merged.memory_mb.clamp(min_mem, config.max_memory_mb);
83
84 let min_timeout = 1.min(config.max_timeout_secs);
85 let timeout_secs = merged
86 .timeout_secs
87 .clamp(min_timeout, config.max_timeout_secs);
88
89 ResourceLimits {
90 cpu_cores,
91 memory_mb,
92 timeout_secs,
93 output_bytes: merged.output_bytes.min(config.max_output_bytes),
94 allow_network: merged.allow_network && config.allow_network && lang_allows_network,
95 }
96}
97
98#[cfg(all(test, feature = "server"))]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_clamp_limits() {
104 let raw = ResourceLimits {
105 cpu_cores: 5.0,
106 memory_mb: 4096,
107 timeout_secs: 120,
108 output_bytes: 9999999,
109 allow_network: true,
110 };
111 let clamped = clamp_limits(raw, true);
112 assert!(clamped.cpu_cores <= 2.0);
113 assert!(clamped.memory_mb <= 1024);
114 assert!(clamped.timeout_secs <= 30);
115 assert!(clamped.output_bytes <= 1048576);
116 assert!(!clamped.allow_network);
117 }
118
119 #[test]
120 fn test_clamp_limits_safeguarded() {
121 let config = RunnerConfig {
122 max_cpu_cores: 0.05,
123 max_memory_mb: 8,
124 max_timeout_secs: 0,
125 max_output_bytes: 100,
126 max_source_bytes: 100,
127 allow_network: true,
128 max_concurrent: 1,
129 queue_timeout_secs: 1,
130 task_ttl_secs: 1,
131 docker_socket_path: "".to_string(),
132 languages: None,
133 };
134 let raw = ResourceLimits {
135 cpu_cores: 1.0,
136 memory_mb: 64,
137 timeout_secs: 10,
138 output_bytes: 50,
139 allow_network: true,
140 };
141 let clamped = clamp_limits_impl(raw, true, &config);
142 assert_eq!(clamped.cpu_cores, 0.05);
143 assert_eq!(clamped.memory_mb, 8);
144 assert_eq!(clamped.timeout_secs, 0);
145 assert_eq!(clamped.output_bytes, 50);
146 assert!(clamped.allow_network);
147 }
148}