feat!: add cooperative execution-time limits across engine, VM, and binding (#539)

- Introduce ExecutionTimer/ExecutionTimerConfig to allow limiting evaluating time.
- To amortize time checking costs, checking interval can be configured via the notion of work units
- A global fallback time limit can be set to universally limit all evaluation in addition to engine level limit setting.
- Implement limnits in interpreter and RVM. In RVM, also handle suspend/resume so that time during pause is not counted.
- Add engine-level APIs to set/clear per-engine timer configuration and apply global fallback defaults.
- Surface execution-time limits through FFI and C# bindings
- Add C# tests and example usage to validate engine overrides, global fallback behavior, and compiled policy enforcement.
- Expand docs for execution-time limit
- Add interpreter YAML cases and VM unit tests for time-limit behavior and deterministic time sources.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2026-01-28 05:58:03 +05:30
committed by GitHub
parent e68e852ee3
commit 394625d4bc
32 changed files with 2259 additions and 183 deletions
+1
View File
@@ -52,6 +52,7 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
})
}
/// Configure the execution timer for evaluations of this compiled policy.
/// Get information about the compiled policy including metadata about modules,
/// target configuration, and resource types.
///
+34
View File
@@ -5,6 +5,7 @@ use crate::common::{
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, RegorusResult, RegorusStatus,
};
use crate::compiled_policy::RegorusCompiledPolicy;
use crate::limits::RegorusExecutionTimerConfig;
use crate::lock::{new_handle, read, try_read, try_write, Handle, ReadGuard, WriteGuard};
use crate::panic_guard::with_unwind_guard;
use alloc::boxed::Box;
@@ -450,6 +451,39 @@ pub extern "C" fn regorus_engine_set_strict_builtin_errors(
})
}
#[no_mangle]
/// Configure the execution timer for a specific engine instance.
pub extern "C" fn regorus_engine_set_execution_timer_config(
engine: *mut RegorusEngine,
config: *const RegorusExecutionTimerConfig,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let config = unsafe {
config
.as_ref()
.copied()
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
};
let mut guard = engine.try_write()?;
guard.set_execution_timer_config(config.to_execution_timer_config()?);
Ok(())
}())
}
#[no_mangle]
/// Clear the engine-specific execution timer configuration.
pub extern "C" fn regorus_engine_clear_execution_timer_config(
engine: *mut RegorusEngine,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let mut guard = engine.try_write()?;
guard.clear_execution_timer_config();
Ok(())
}())
}
/// Get pretty printed coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
+44 -1
View File
@@ -1,8 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::common::{RegorusResult, RegorusStatus};
use crate::common::{to_regorus_result, RegorusResult, RegorusStatus};
use alloc::format;
use anyhow::{anyhow, Result};
use core::num::NonZeroU32;
use core::time::Duration;
use regorus::utils::limits::{self, ExecutionTimerConfig};
#[cfg(feature = "allocator-memory-limits")]
fn some_or_none(flag: bool, value: u64) -> Option<u64> {
@@ -131,6 +135,45 @@ fn feature_disabled(function: &str) -> RegorusResult {
)
}
/// FFI representation of [`ExecutionTimerConfig`].
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RegorusExecutionTimerConfig {
/// Wall-clock limit expressed in nanoseconds.
pub limit_ns: u64,
/// Number of work units between timer checks (must be non-zero).
pub check_interval: u32,
}
impl RegorusExecutionTimerConfig {
pub fn to_execution_timer_config(self) -> Result<ExecutionTimerConfig> {
let check_interval = NonZeroU32::new(self.check_interval)
.ok_or_else(|| anyhow!("execution_timer.check_interval must be non-zero"))?;
let limit = Duration::from_nanos(self.limit_ns);
Ok(ExecutionTimerConfig {
limit,
check_interval,
})
}
}
#[no_mangle]
pub extern "C" fn regorus_set_fallback_execution_timer_config(
config: RegorusExecutionTimerConfig,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
limits::set_fallback_execution_timer_config(Some(config.to_execution_timer_config()?));
Ok(())
}())
}
#[no_mangle]
pub extern "C" fn regorus_clear_fallback_execution_timer_config() -> RegorusResult {
limits::set_fallback_execution_timer_config(None);
RegorusResult::ok_void()
}
#[cfg(test)]
mod tests {
use super::{