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
impl ResourceTracker
Sourcepub const LOOP_CHECK_INTERVAL: usize = 64
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.
Sourcepub fn tighten_limits(&mut self, ceiling: &ResourceLimits)
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.
Sourcepub fn new(limits: ResourceLimits) -> Self
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.
Sourcepub fn elapsed(&self) -> Duration
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.
Sourcepub fn feed_elapsed(&self) -> Duration
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.
Sourcepub fn turn_elapsed(&self) -> Duration
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.
Sourcepub fn max_feed_duration(&self) -> Option<Duration>
pub fn max_feed_duration(&self) -> Option<Duration>
Returns the configured per-feed execution time limit, if any.
Sourcepub fn max_turn_duration(&self) -> Option<Duration>
pub fn max_turn_duration(&self) -> Option<Duration>
Returns the configured per-turn execution time limit, if any.
Sourcepub fn max_memory(&self) -> Option<usize>
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.
Sourcepub fn max_suspensions(&self) -> usize
pub fn max_suspensions(&self) -> usize
Returns the host-enforced suspension budget (default
DEFAULT_MAX_SUSPENSIONS; never unlimited).
Sourcepub fn max_total_sleep(&self) -> Option<Duration>
pub fn max_total_sleep(&self) -> Option<Duration>
Cumulative sleep limit for the host to enforce, including after restore.
Sourcepub fn has_memory_time_limit(&self) -> bool
pub fn has_memory_time_limit(&self) -> bool
Returns whether the VM has a memory or time limit configured.
Sourcepub fn has_time_limit(&self) -> bool
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.
Sourcepub fn set_max_feed_duration(&mut self, duration: Duration)
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.
Sourcepub fn set_max_turn_duration(&mut self, duration: Duration)
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.
Sourcepub fn check_allocation(&self, additional: usize) -> Result<(), ResourceError>
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.
Sourcepub fn check_memory_time(&self) -> Result<(), ResourceError>
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.
Sourcepub fn check_time(&self) -> Result<(), ResourceError>
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.
Sourcepub fn check_time_every(&self, i: usize) -> Result<(), ResourceError>
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.
Sourcepub fn check_memory_time_every(&self, i: usize) -> Result<(), ResourceError>
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.
Sourcepub fn check_growth(
&self,
len: usize,
capacity: usize,
elem_size: usize,
) -> Result<(), ResourceError>
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.
Sourcepub fn growth_bytes(len: usize, capacity: usize, elem_size: usize) -> usize
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.
Sourcepub fn check_pending_allocation(
&self,
additional: usize,
) -> Result<(), ResourceError>
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.
Sourcepub fn check_recursion_depth(
&self,
current_depth: usize,
) -> Result<(), ResourceError>
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.
Sourcepub fn check_large_result(
&self,
estimated_bytes: usize,
) -> Result<(), ResourceError>
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.
Sourcepub fn gc_interval(&self) -> Option<usize>
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.
Sourcepub fn on_execution_start(&self)
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.
Sourcepub fn on_execution_stop(&self)
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.
Sourcepub fn on_feed_start(&self)
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.
Sourcepub fn on_turn_start(&self)
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.
Sourcepub fn sandbox_sleep(&self, duration: Duration)
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.