Skip to main content

ResourceTracker

Struct ResourceTracker 

Source
pub struct ResourceTracker { /* private fields */ }
Expand description

A resource tracker that enforces configurable limits.

Checks allocator-backed memory usage and tracks execution time, returning errors when limits are exceeded. It also schedules garbage collection.

Uses Cell for mutable timing and recursion state behind shared references.

The two duration limits share one execution time clock: it runs only between the outermost on_execution_start/on_execution_stop pair, so it is paused while suspended on the host and between REPL feeds. They differ only in when their accumulator resets — at on_feed_start, at on_turn_start — and the feed total is serialized, so a session loaded mid-feed resumes that budget rather than restarting from zero.

Implementations§

Source§

impl ResourceTracker

Source

pub const LOOP_CHECK_INTERVAL: usize = 64

Items processed between full checks in amortized per-item Rust loops (see check_time_every). A limit can be overshot by up to this many items’ work before the next check — an accepted trade for cheap loops; the process-level hard limits backstop pathological cases.

Source

pub fn tighten_limits(&mut self, ceiling: &ResourceLimits)

Tightens restored limits to the destination policy without resetting execution clocks. A snapshot can retain stricter limits but cannot increase the destination’s allowance.

Source

pub fn new(limits: ResourceLimits) -> Self

Creates a new ResourceTracker with the given limits.

The execution-time clock starts at zero and only runs while the VM executes, so the tracker can be created any amount of time before the first run without consuming a duration budget. A configured max_memory requires monty-alloc installed as the global allocator and armed via set_hard_limit(memory_limit_with_headroom(...)); otherwise it is silently not enforced.

Source

pub fn elapsed(&self) -> Duration

Returns the cumulative execution time: bytecode-execution wall time accumulated across runs/feeds, excluding time suspended on the host or idle between feeds. Includes the in-progress window if the VM is currently executing.

Nothing is bounded by this — it is reported to the host for telemetry. The budgets are feed_elapsed and turn_elapsed.

Source

pub fn feed_elapsed(&self) -> Duration

Returns the execution time consumed by the current feed; see elapsed, which reports the same clock over the whole session.

Source

pub fn turn_elapsed(&self) -> Duration

Returns the execution time consumed by the current host turn; see elapsed, which reports the same clock over the whole session.

Source

pub fn max_feed_duration(&self) -> Option<Duration>

Returns the configured per-feed execution time limit, if any.

Source

pub fn max_turn_duration(&self) -> Option<Duration>

Returns the configured per-turn execution time limit, if any.

Source

pub fn max_memory(&self) -> Option<usize>

Returns the configured memory budget, if any. Hosts that bound a worker process from outside the interpreter size that bound from this.

Source

pub fn max_suspensions(&self) -> usize

Returns the host-enforced suspension budget (default DEFAULT_MAX_SUSPENSIONS; never unlimited).

Source

pub fn max_total_sleep(&self) -> Option<Duration>

Cumulative sleep limit for the host to enforce, including after restore.

Source

pub fn has_memory_time_limit(&self) -> bool

Returns whether the VM has a memory or time limit configured.

Source

pub fn has_time_limit(&self) -> bool

Returns whether either execution-time budget is configured.

Public so callers that pay for finer-grained clock polling only when a budget exists (fstring’s incremental large-result path) can ask.

Source

pub fn set_max_feed_duration(&mut self, duration: Duration)

Sets the per-feed execution limit as a fresh budget from now, resetting the feed (and so the turn) clock.

This lets a host enforce a different (typically shorter) time limit for a resumed phase — e.g. allowing a long build phase, then giving repr() of the result only a few milliseconds. Time spent suspended in the host never counts toward the budget either way.

Source

pub fn set_max_turn_duration(&mut self, duration: Duration)

Sets the per-turn execution limit as a fresh budget from now, resetting the turn clock.

Source

pub fn check_allocation(&self, additional: usize) -> Result<(), ResourceError>

Checks whether one up-front allocation fits the memory budget.

Use this before reserving a buffer that could cross both the soft and hard allocator limits before execution reaches another checkpoint.

Source

pub fn check_memory_time(&self) -> Result<(), ResourceError>

Called periodically to check allocator-backed memory and time limits.

Returns Ok(()) while configured limits are respected, or the relevant resource error once either limit is exceeded.

Takes &self rather than &mut self because checking elapsed time is a read-only operation. This allows time checks in contexts that only have an immutable heap reference, such as py_repr_fmt.

Source

pub fn check_time(&self) -> Result<(), ResourceError>

Called periodically to check both execution-time budgets.

Each clock is monotonic within its scope, so once a budget is exceeded every later call in that scope fails too. The feed budget is tested first, since it is the one a new turn cannot recover from.

Source

pub fn check_time_every(&self, i: usize) -> Result<(), ResourceError>

Amortized per-item time check for Rust-side loops: a full clock read once per LOOP_CHECK_INTERVAL calls, free otherwise. Key i on the loop’s index or a monotonically increasing counter. Fires at the end of each block (i % N == N-1) so loops shorter than the interval pay no clock read at all — the VM dispatch checkpoint covers cadence between short calls.

Source

pub fn check_memory_time_every(&self, i: usize) -> Result<(), ResourceError>

Amortized per-item memory + time check; the memory-probing sibling of check_time_every, for loops that allocate per item. Between full checks the allocator’s hard limit still bounds runaway growth.

Source

pub fn check_growth( &self, len: usize, capacity: usize, elem_size: usize, ) -> Result<(), ResourceError>

Preflights the reallocation that pushing one more element onto a dense buffer causes; a push that fits the existing capacity costs nothing.

A Vec charges its whole doubling in one allocation, so a push straddling the soft limit can land past the allocator’s fixed hard-limit headroom, killing the worker with no checkpoint in between at which to raise MemoryError. Only for the one-push shape: a bulk reservation needs ResourceTracker::check_allocation sized for the whole result, since preflighting less than the final buffer — one operand of a merge, say — leaves the same window open.

Source

pub fn growth_bytes(len: usize, capacity: usize, elem_size: usize) -> usize

The bytes check_growth would preflight, or zero if the push allocates nothing.

Split out for containers that grow two buffers on one insertion, such as a dict’s entry vector and index table: checking each increment alone passes both while their sum clears the headroom, so the caller sums them and passes the total to check_pending_allocation.

Source

pub fn check_pending_allocation( &self, additional: usize, ) -> Result<(), ResourceError>

check_allocation for preflights whose increment may be zero: a push that allocates nothing must not pay for the usage probe.

Source

pub fn check_recursion_depth( &self, current_depth: usize, ) -> Result<(), ResourceError>

Called before pushing a new call frame to check recursion depth.

Returns Ok(()) if within recursion limit, or Err(ResourceError::Recursion) if the limit would be exceeded. current_depth is the call stack depth before the new frame is pushed.

Source

pub fn check_large_result( &self, estimated_bytes: usize, ) -> Result<(), ResourceError>

Called before operations that may produce large results (>100KB).

This allows pre-emptive rejection of operations like 2 ** 10_000_000 before the memory is actually allocated. The check only happens for estimated result sizes above LARGE_RESULT_THRESHOLD to avoid overhead on small operations.

Source

pub fn gc_interval(&self) -> Option<usize>

Returns the configured garbage collection interval, in GC-tracked allocations.

The cycle collector runs at most once per gc_interval GC-tracked allocations, and additionally short-circuits when no cycle candidates are pending — so programs that never form cycles pay no collector cost regardless of their allocation rate. None tells the heap to use its built-in default scheduling threshold.

Source

pub fn on_execution_start(&self)

Called when the VM enters its execution loop from a host boundary (VM::run_external), starting one execution window.

Paired with on_execution_stop and never nested — VM-internal re-entry (task switches, host-initiated function evaluation) uses the raw run loop, so its time falls inside the enclosing window. The execution-time clock runs between the pair; it is not running while execution is suspended waiting on the host (external function calls) or between feeds.

Source

pub fn on_execution_stop(&self)

Called when the VM leaves its execution loop — on completion, error, or suspension at an external call. See on_execution_start.

Source

pub fn on_feed_start(&self)

Called when the host begins a new feed, resetting the max_feed_duration budget (and, since a feed opens a turn, the max_turn_duration one).

A feed spans every turn its snippet takes, so this must fire only at the snippet’s first turn — not at the resumes that continue it.

Source

pub fn on_turn_start(&self)

Called when the host hands control back to the interpreter — at a feed or a resume — resetting the max_turn_duration budget.

Continuations the VM resolves without the host (an already-settled future, a task switch) stay inside the turn that started them.

Source

pub fn sandbox_sleep(&self, duration: Duration)

Performs a standard-execution sleep without charging max_feed_duration. Restarts the execution clock only if it was running before the wait. All interpreter waits use block_for for platform-specific blocking.

Trait Implementations§

Source§

impl Debug for ResourceTracker

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ResourceTracker

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ResourceTracker

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for ResourceTracker

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.