use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub max_instructions: Option<u64>,
pub max_memory_bytes: Option<u64>,
pub max_wall_time: Option<Duration>,
pub max_output_bytes: Option<u64>,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_instructions: None,
max_memory_bytes: None,
max_wall_time: None,
max_output_bytes: None,
}
}
}
impl ResourceLimits {
pub fn unlimited() -> Self {
Self::default()
}
pub fn sandboxed() -> Self {
Self {
max_instructions: Some(10_000_000),
max_memory_bytes: Some(256 * 1024 * 1024), max_wall_time: Some(Duration::from_secs(30)),
max_output_bytes: Some(1024 * 1024), }
}
}
#[derive(Debug)]
pub struct ResourceUsage {
pub instructions_executed: u64,
pub memory_bytes_allocated: u64,
pub output_bytes_written: u64,
start_time: Option<Instant>,
limits: ResourceLimits,
wall_time_check_interval: u64,
instructions_since_time_check: u64,
}
#[derive(Debug, Clone)]
pub enum ResourceLimitExceeded {
InstructionLimit { limit: u64, executed: u64 },
MemoryLimit { limit: u64, allocated: u64 },
WallTimeLimit { limit: Duration, elapsed: Duration },
OutputLimit { limit: u64, written: u64 },
}
impl std::fmt::Display for ResourceLimitExceeded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InstructionLimit { limit, executed } => {
write!(f, "Instruction limit exceeded: {executed} >= {limit}")
}
Self::MemoryLimit { limit, allocated } => {
write!(
f,
"Memory limit exceeded: {allocated} bytes >= {limit} bytes"
)
}
Self::WallTimeLimit { limit, elapsed } => {
write!(f, "Wall time limit exceeded: {elapsed:?} >= {limit:?}")
}
Self::OutputLimit { limit, written } => {
write!(f, "Output limit exceeded: {written} bytes >= {limit} bytes")
}
}
}
}
impl ResourceUsage {
pub fn new(limits: ResourceLimits) -> Self {
Self {
instructions_executed: 0,
memory_bytes_allocated: 0,
output_bytes_written: 0,
start_time: None,
limits,
wall_time_check_interval: 1024,
instructions_since_time_check: 0,
}
}
pub fn start(&mut self) {
self.start_time = Some(Instant::now());
}
#[inline]
pub fn tick_instruction(&mut self) -> Result<(), ResourceLimitExceeded> {
self.instructions_executed += 1;
if let Some(limit) = self.limits.max_instructions {
if self.instructions_executed >= limit {
return Err(ResourceLimitExceeded::InstructionLimit {
limit,
executed: self.instructions_executed,
});
}
}
self.instructions_since_time_check += 1;
if self.instructions_since_time_check >= self.wall_time_check_interval {
self.instructions_since_time_check = 0;
self.check_wall_time()?;
}
Ok(())
}
pub fn record_allocation(&mut self, bytes: u64) -> Result<(), ResourceLimitExceeded> {
self.memory_bytes_allocated += bytes;
if let Some(limit) = self.limits.max_memory_bytes {
if self.memory_bytes_allocated >= limit {
return Err(ResourceLimitExceeded::MemoryLimit {
limit,
allocated: self.memory_bytes_allocated,
});
}
}
Ok(())
}
pub fn record_output(&mut self, bytes: u64) -> Result<(), ResourceLimitExceeded> {
self.output_bytes_written += bytes;
if let Some(limit) = self.limits.max_output_bytes {
if self.output_bytes_written >= limit {
return Err(ResourceLimitExceeded::OutputLimit {
limit,
written: self.output_bytes_written,
});
}
}
Ok(())
}
fn check_wall_time(&self) -> Result<(), ResourceLimitExceeded> {
if let (Some(limit), Some(start)) = (self.limits.max_wall_time, self.start_time) {
let elapsed = start.elapsed();
if elapsed >= limit {
return Err(ResourceLimitExceeded::WallTimeLimit { limit, elapsed });
}
}
Ok(())
}
pub fn limits(&self) -> &ResourceLimits {
&self.limits
}
pub fn elapsed(&self) -> Option<Duration> {
self.start_time.map(|s| s.elapsed())
}
}