Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: ./.github/workflows/python_tests.yml
with:
hypothesis-profile: "ci"
parallelism: "48"
parallelism: "logical"
pytest-add-params:
"-m 'not slow' --max-tests=5000 --randomly-seed=$GITHUB_RUN_ID
cairo/tests/ef_tests/"
Expand Down
50 changes: 48 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
)
from hypothesis import HealthCheck, Phase, Verbosity, settings

# Import memory management functionality
from cairo_addons.testing.memory_manager import (
get_memory_requirements_for_ci,
wait_for_memory,
)

load_dotenv()
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
Expand Down Expand Up @@ -117,8 +123,8 @@ def pytest_configure(config):
# Patching evm_trace:
# - Problem: Global patches of `ethereum.trace.evm_trace` are not reflected in places where `evm_trace` is imported in EELS.
# - Cause: `ethereum.prague.vm.interpreter` (and other modules) imports `evm_trace` locally (e.g., `from ethereum.trace import evm_trace`)
# at module load time, caching the original `discard_evm_trace`. Patching `ethereum.trace.evm_trace` later didnt
# update this local reference due to Pythons import caching.
# at module load time, caching the original `discard_evm_trace`. Patching `ethereum.trace.evm_trace` later didn't
# update this local reference due to Python's import caching.
# - Solution: Explicitly patch both `ethereum.trace.evm_trace` globally and
# `ethereum.prague.vm.interpreter.evm_trace` locally (and other places where `evm_trace` is imported).
if config.getoption("log_cli_level") == "TRACE":
Expand Down Expand Up @@ -182,3 +188,43 @@ def seed(request):
)
settings.load_profile(os.getenv("HYPOTHESIS_PROFILE", "default"))
logger.info(f"Using Hypothesis profile: {os.getenv('HYPOTHESIS_PROFILE', 'default')}")


def pytest_addoption(parser):
"""Add memory management options to pytest."""
parser.addoption(
"--disable-memory-management",
action="store_true",
default=False,
help="Disable memory monitoring before test execution",
)
parser.addoption(
"--min-available-memory-gb",
action="store",
default=None,
type=float,
help="Minimum available memory in GB before running tests",
)


def pytest_runtest_setup(item):
"""
Hook that runs before each test to ensure sufficient memory is available.
"""
config = item.config
# Skip memory check if disabled
if config.getoption("disable_memory_management"):
return

# Get memory requirements - prefer CLI options, fall back to CI detection
memory_reqs = get_memory_requirements_for_ci()

# Override with CLI options if provided
if config.getoption("min_available_memory_gb") is not None:
memory_reqs["min_available_gb"] = config.getoption("min_available_memory_gb")

# Wait for memory to be available
memory_available = wait_for_memory(**memory_reqs)

if not memory_available:
pytest.fail(f"Test {item.nodeid} failed due to insufficient memory")
3 changes: 3 additions & 0 deletions dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,6 @@ EEST
exitstatus
PREDEPLOY
bilinearity
reqs
runtest
psutil
46 changes: 46 additions & 0 deletions docs/AI-REPORT.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# AI-Reports

## AI-REPORT: Memory Management for pytest-xdist Workers (June 3, 2025)

### Problem

CI jobs were crashing with "node down: Not properly terminated" errors when
running pytest-xdist with high parallelism (48 workers for EF tests). Memory
exhaustion during test execution caused worker processes to crash, interrupting
the test suite.

### Solution

Implemented a simple hook-based memory management system that pauses test
execution when memory is insufficient:

1. **Pre-Test Memory Check**: Added `pytest_runtest_setup` hook that checks
available memory before each test runs
2. **Automatic Pausing**: When memory is low, test execution pauses until
sufficient memory becomes available
3. **Environment-Aware Thresholds**: Different memory requirements for CI (3GB
free, max 90% usage) vs local development (1GB free, max 90% usage)
4. **Zero Configuration**: Works out of the box with automatic CI detection

### Implementation

- **`memory_manager.py`**: Simple utilities with `wait_for_memory()` function
and environment detection
- **`hooks.py`**: Added memory check in `pytest_runtest_setup` hook before each
test
- **`pyproject.toml`**: Added `psutil>=6.1.0` dependency for memory monitoring

### Key Features

- **Non-intrusive**: No changes to worker counts or pytest-xdist configuration
- **Automatic Recovery**: Workers resume when memory becomes available
- **Timeout Protection**: Tests fail gracefully if memory doesn't become
available within timeout (2 minutes CI, 2 minutes local)
- **Configurable**: CLI options for custom thresholds
(`--min-available-memory-gb`, `--max-memory-percent`,
`--disable-memory-management`)

### Impact

Prevents CI crashes while maintaining maximum parallelism. Workers automatically
pause/resume based on memory availability, eliminating "node down" errors
without reducing test concurrency.

## AI-REPORT: Cairo PIE Support and AR Inputs Generation for External Users (May 30, 2025)

### Context & Motivation
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dev-dependencies = [
"pyinstrument>=5.0.0",
"gitpython>=3.1.44",
"filelock>=3.17.0",
"psutil>=6.1.0",
]

[tool.uv.sources]
Expand Down
120 changes: 120 additions & 0 deletions python/cairo-addons/src/cairo_addons/testing/memory_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
Memory Management for pytest-xdist Workers

This module provides utilities to monitor memory usage during test execution
and pause workers when memory is insufficient to prevent CI crashes.
"""

import logging
import os
import time

try:
import psutil

PSUTIL_AVAILABLE = True
except ImportError:
PSUTIL_AVAILABLE = False

logger = logging.getLogger(__name__)


def get_memory_info():
"""Get current memory usage information."""
if not PSUTIL_AVAILABLE:
return None

memory = psutil.virtual_memory()
return {
"total": memory.total / (1024**3), # GB
"available": memory.available / (1024**3), # GB
"percent_used": memory.percent,
"free": memory.free / (1024**3), # GB
}


def wait_for_memory(
min_available_gb: float = 2.0,
check_interval: float = 1.0,
max_wait_time: float = 120.0, # 2 minutes max wait
) -> bool:
"""
Wait until sufficient memory is available.

Args:
min_available_gb: Minimum available memory in GB
check_interval: How often to check memory (seconds)
max_wait_time: Maximum time to wait (seconds)

Returns:
True if memory is available, False if timed out
"""
if not PSUTIL_AVAILABLE:
return True # Can't check, assume it's fine

start_time = time.time()
first_check = True

while time.time() - start_time < max_wait_time:
memory_info = get_memory_info()
if not memory_info:
return True # Can't check, assume it's fine

# Check if memory conditions are met
memory_ok = memory_info["available"] >= min_available_gb

if memory_ok:
if not first_check:
logger.info(
f"Memory available again: {memory_info['available']:.1f}GB free, "
f"{memory_info['percent_used']:.1f}% used"
)
return True

if first_check:
logger.warning(
f"Waiting for memory: need {min_available_gb}GB free "
f"(have {memory_info['available']:.1f}GB), "
f"(current {memory_info['percent_used']:.1f}%)"
)
first_check = False

time.sleep(check_interval)

# Timed out
memory_info = get_memory_info()
logger.error(
f"Timed out waiting for memory after {max_wait_time}s. "
f"Current: {memory_info['available']:.1f}GB free, "
f"{memory_info['percent_used']:.1f}% used"
)
return False


def get_memory_requirements_for_ci():
"""
Get memory requirements optimized for CI environment.

Returns:
Dict with memory thresholds for CI
"""
# Check if we're in a CI environment
is_ci = any(
env in os.environ
for env in ["CI", "GITHUB_ACTIONS", "GITLAB_CI", "JENKINS_URL"]
)

if is_ci:
# More conservative settings for CI
return {
"min_available_gb": 2.0, # Need at least 2GB free
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"min_available_gb": 2.0, # Need at least 2GB free
"min_available_gb": 2.0, # Needs at least 2GB free

"check_interval": 1.0, # Check Every Second
"max_wait_time": 60.0,
}
else:
# More relaxed settings for local development
return {
"min_available_gb": 1.0, # Need at least 1GB free
"check_interval": 1.0, # Check every second
"max_wait_time": 60.0,
}
2 changes: 2 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading