# How to Configure Monty's Resource Limits: Memory, Time, and Recursion Depth

> Master Monty's resource limits! Learn to configure max memory, duration, allocations, and recursion depth for robust sandbox execution. Ensure secure and efficient code.

- Repository: [Pydantic/monty](https://github.com/pydantic/monty)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Configure Monty's resource limits by creating a `ResourceLimits` struct (Rust) or `ResourceLimits` TypedDict (Python) with values for `max_memory`, `max_duration`, `max_allocations`, and `max_recursion_depth`, then pass it to `LimitedTracker` in Rust or the `resource_limits` parameter in Python to enforce sandbox boundaries.**

Monty is a sandboxed Python interpreter developed by Pydantic that executes untrusted code safely through strict resource constraints. To prevent denial-of-service attacks from runaway memory consumption, infinite loops, or stack overflow exploits, you must configure Monty's resource limits before executing arbitrary code. This guide explains the `ResourceTracker` architecture and provides complete implementation examples for both Rust and Python environments.

## Understanding Monty's Resource Tracking Architecture

Monty implements resource limits through the `ResourceTracker` trait defined in [`crates/monty/src/resource.rs`](https://github.com/pydantic/monty/blob/main/crates/monty/src/resource.rs). This trait provides hooks that the virtual machine calls during critical operations to verify that configured thresholds remain within bounds.

### Core Components

The resource management system consists of several key types that work together to enforce constraints:

- **`ResourceLimits`** – A configuration struct using the builder pattern to set optional caps on memory, time, allocations, and recursion depth.
- **`LimitedTracker`** – The primary implementation of `ResourceTracker` that enforces the constraints defined in `ResourceLimits`.
- **`NoLimitTracker`** – A permissive implementation that only enforces the default recursion limit of 1000 frames without restricting memory or execution time.
- **`PySignalTracker`** – A wrapper available in the Python bindings that adds Ctrl+C handling by checking Python signals periodically.

## Configuring Resource Limits in Rust

To configure limits in a Rust application using Monty, instantiate `ResourceLimits` using the builder pattern and pass it to `LimitedTracker`.

```rust
use monty::{
    ResourceLimits, LimitedTracker, DEFAULT_MAX_RECURSION_DEPTH,
    ResourceTracker,
};
use std::time::Duration;

// Configure specific resource constraints
let limits = ResourceLimits::new()
    .max_allocations(10_000)                // Maximum 10,000 heap allocations
    .max_memory(5 * 1024 * 1024)            // Maximum 5 MiB heap usage
    .max_duration(Duration::from_secs(2))   // Maximum 2 seconds CPU time
    .gc_interval(500)                       // Run GC every 500 allocations
    .max_recursion_depth(Some(200));        // Maximum 200 call-stack frames

// Create the tracker that enforces these limits
let mut tracker = LimitedTracker::new(limits);

// Execute code with the tracker
let code = "def fib(n):\n    return n if n<2 else fib(n-1)+fib(n-2)\nfib(30)";
let mut monty = Monty::new(code)?;
monty.run_with_tracker(&mut tracker)?;

```

The `LimitedTracker` provides methods to inspect current usage, including `allocation_count()`, `current_memory()`, and `elapsed()`. You can also adjust time limits dynamically between executions using `set_max_duration()`.

## Configuring Resource Limits in Python

When using Monty through its Python bindings, pass a `ResourceLimits` TypedDict to the `Monty` constructor. The extraction logic resides in [`crates/monty-python/src/limits.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-python/src/limits.rs).

```python
from pydantic_monty import Monty, ResourceLimits

# Define resource constraints as a TypedDict

limits = ResourceLimits(
    max_allocations=20_000,
    max_duration_secs=1.5,           # Seconds as float

    max_memory=4 * 1024 * 1024,      # 4 MiB

    gc_interval=250,
    max_recursion_depth=300,
)

# Instantiate Monty with these limits

code = """
def fact(n):
    return 1 if n==0 else n*fact(n-1)
fact(25)
"""
m = Monty(code, resource_limits=limits)

# Execute - raises MemoryError, TimeoutError, or RecursionError on breach

result = m.run()
print(result)

```

The Python wrapper automatically maps resource violations to standard Python exceptions. Memory and allocation limits raise `MemoryError`, time limits raise `TimeoutError`, and recursion depth limits raise `RecursionError`.

## How Monty Enforces Resource Limits Internally

Understanding the enforcement mechanisms helps you tune limits effectively for specific workloads.

### Allocation and Memory Tracking

Every heap allocation in Monty invokes `tracker.on_allocate(|| size)` before committing memory. The `LimitedTracker` maintains atomic counters for both allocation count and total bytes. If `max_allocations` or `max_memory` would be exceeded, it returns `ResourceError::Allocation` or `ResourceError::Memory`, which the VM translates to Python `MemoryError`.

### Execution Time Limits

Time checking uses a sampling strategy to minimize overhead. The VM calls `tracker.check_time()` after every instruction, but `LimitedTracker` only evaluates `Instant::elapsed()` every 10 calls (`TIME_CHECK_INTERVAL`). When elapsed time exceeds `max_duration`, it raises `ResourceError::Time`, converted to Python `TimeoutError`.

### Recursion Depth Limits

Before pushing a new call frame, the VM queries `tracker.check_recursion_depth(current_depth)`. The default limit mirrors CPython's 1000 frames, but you can override this via `max_recursion_depth` in `ResourceLimits`. Internal operations like `repr()` and `hash()` also respect these limits through `DepthGuard` structures defined in [`crates/monty/src/resource.rs`](https://github.com/pydantic/monty/blob/main/crates/monty/src/resource.rs).

### Large Result Protection

Operations that could generate massive intermediate values—such as `x * 10**9` or `2**100000`—first call `check_large_result(estimated_bytes)`. The estimate uses bit-size heuristics to prevent accidental denial-of-service from huge integer or string allocations.

### Python Signal Integration

When using the Python bindings, `PySignalTracker` wraps your chosen tracker (typically `LimitedTracker`). Its `check_time` implementation forwards to the inner tracker, then every 1000 calls invokes `py.check_signals()` (wrapping `PyErr_CheckSignals`). If the user presses Ctrl+C, the next signal check raises `KeyboardInterrupt`, allowing immediate termination of long-running or infinite loops without waiting for other resource limits to trigger.

## Summary

Configuring Monty's resource limits involves these essential steps:

- Define constraints using `ResourceLimits` with the builder pattern in Rust or the TypedDict interface in Python to cap **memory**, **time**, **allocations**, and **recursion depth**.
- Instantiate `LimitedTracker` in Rust or pass `resource_limits` to the `Monty` constructor in Python to activate enforcement.
- Understand that the VM checks limits via `ResourceTracker` trait methods: `on_allocate`, `check_time`, and `check_recursion_depth`.
- Recognize that Python bindings automatically map violations to standard exceptions: `MemoryError`, `TimeoutError`, and `RecursionError`.

## Frequently Asked Questions

### What happens when a resource limit is exceeded in Monty?

When a limit is breached, the `LimitedTracker` returns a `ResourceError` variant (Allocation, Memory, Time, or Recursion) which the Monty VM converts to a standard Python exception. Memory and allocation limits raise `MemoryError`, time limits raise `TimeoutError`, and recursion depth limits raise `RecursionError`. In Rust, these appear as `Err(ResourceError)` results that you must handle explicitly in your application code.

### Can I change resource limits after creating the Monty instance?

Yes, but with specific constraints. In Rust, you can mutate the `LimitedTracker` between executions using methods like `set_max_duration()` to adjust the time limit dynamically. However, the `ResourceLimits` struct itself is typically consumed during tracker construction. In Python, you must create a new `Monty` instance with updated `resource_limits` parameters, as the limits are bound at initialization and cannot be modified on existing instances.

### How does Monty handle Ctrl+C interruptions during execution?

Monty supports graceful interruption through the `PySignalTracker` wrapper available in the Python bindings. When you configure resource limits via Python, the `LimitedTracker` is automatically wrapped in a `PySignalTracker` that checks for pending Python signals every 1000 VM instructions. If the user presses Ctrl+C, the next signal check raises `KeyboardInterrupt`, allowing immediate termination of long-running or infinite loops without waiting for other resource limits to trigger.

### What is the default recursion depth limit in Monty?

By default, Monty uses a recursion depth limit of **1000** frames, matching CPython's standard behavior. This default is enforced by the `NoLimitTracker` when no explicit limits are configured, and by `LimitedTracker` when `max_recursion_depth` is set to `None` or omitted from the configuration. You can override this to any positive integer (or `None` for unlimited) via the `ResourceLimits` configuration in both Rust and Python interfaces, though setting unlimited recursion is not recommended for sandboxed environments.