BoxLite provides a structured exception hierarchy for error handling and metrics classes for monitoring resource usage across boxes.
Error Types
from boxlite import BoxliteError, ExecError, TimeoutError, ParseError
Exception Hierarchy
BoxliteError (base)
├── ExecError # Command execution failed
├── TimeoutError # Operation timed out
└── ParseError # Output parsing failed
BoxliteError
Base exception for all BoxLite errors. Catch this to handle any BoxLite-specific error.
try:
async with SimpleBox(image="invalid:image") as box:
pass
except BoxliteError as e:
print(f"BoxLite error: {e}")
ExecError
Raised when a command execution fails (non-zero exit code).
| Attribute | Type | Description |
|---|
command | str | The command that failed |
exit_code | int | Non-zero exit code |
stderr | str | Standard error output |
try:
result = await box.exec("false") # Exit code 1
except ExecError as e:
print(f"Command: {e.command}")
print(f"Exit code: {e.exit_code}")
print(f"Stderr: {e.stderr}")
TimeoutError
Raised when an operation times out.
try:
await computer.wait_until_ready(timeout=5)
except TimeoutError:
print("Desktop did not become ready in time")
ParseError
Raised when output parsing fails.
try:
x, y = await computer.cursor_position()
except ParseError:
print("Failed to parse cursor position")
Core Error Variants
BoxLite uses a centralized error enum internally. These are surfaced as BoxliteError exceptions in Python with descriptive messages.
UnsupportedEngine
Platform or hypervisor not supported.
Cause:
- Running on Windows
- Running on Intel Mac
- KVM not available on Linux
- Hypervisor.framework not available on macOS
Example:
Error: unsupported engine kind
Solution:
- Use supported platform (macOS ARM64, Linux x86_64/ARM64)
- Verify hypervisor availability:
- Linux:
grep -E 'vmx|svm' /proc/cpuinfo
- macOS: Ensure macOS 12+ on Apple Silicon
Engine(String)
Hypervisor or VM engine error.
Cause:
- KVM module not loaded
- Insufficient permissions for
/dev/kvm
- Hypervisor.framework error
- VM creation failed
Example:
Error: engine reported an error: KVM is not available
Solution:
# Linux: Load KVM module
sudo modprobe kvm kvm_intel # or kvm_amd
# Linux: Check /dev/kvm permissions
ls -l /dev/kvm
sudo chmod 666 /dev/kvm
# Linux: Add user to kvm group
sudo usermod -aG kvm $USER
# (logout and login required)
Config(String)
Invalid box configuration.
Cause:
- Invalid CPU count (< 1 or > host CPUs)
- Invalid memory size (< 128 or > 65536)
- Invalid paths in volumes
- Invalid port numbers
Example:
Error: configuration error: CPU count must be between 1 and 8
Solution:
- Verify configuration parameters are within valid ranges
- Check file paths exist for volume mounts
- Ensure port numbers are valid (1-65535)
Storage(String)
Filesystem or disk operation error.
Cause:
- Disk full (
~/.boxlite partition)
- Permission denied writing to
~/.boxlite
- Disk image creation failed
- QCOW2 operation failed
Example:
Error: storage error: No space left on device
Solution:
# Check disk space
df -h ~/.boxlite
# Check permissions
ls -ld ~/.boxlite
chmod 755 ~/.boxlite
# Clean up old boxes
# (manually remove ~/.boxlite/boxes/*)
Image(String)
OCI image pull or extraction error.
Cause:
- Network connectivity issues
- Invalid image name or tag
- Registry authentication required
- Image not found in registry
- Corrupted image layers
Example:
Error: images error: failed to pull image: 404 Not Found
Solution:
# Verify image exists
docker pull <image>
# Check network connectivity
ping registry-1.docker.io
# Authenticate for private images
docker login
# Clear image cache if corrupted
rm -rf ~/.boxlite/images/*
Portal(String)
Host-guest communication error (gRPC over vsock).
Cause:
- Guest agent not responding
- vsock connection failed
- gRPC timeout
- Guest initialization failed
Example:
Error: portal error: connection timeout
Solution:
- Enable debug logging:
RUST_LOG=debug
- Check if box is running:
box.info().status
- Restart box:
box.stop() and recreate
- Report issue with logs if persists
Network(String)
Network configuration or connectivity error.
Cause:
- gvproxy not running or crashed
- Port already in use
- Network backend initialization failed
Example:
Error: network error: bind: address already in use
Solution:
# Check port availability
lsof -i :8080
# Stop conflicting process or use different port
ports=[(8081, 80, "tcp")]
# Verify gvproxy binary exists
ls ~/.boxlite/gvproxy/
Execution(String)
Command execution error.
Cause:
- Command not found in image
- Command crashed or killed
- Execution timeout
- Streaming I/O error
Example:
Error: Execution error: command not found: python3
Solution:
-
Verify command exists in image:
result = await box.exec("which", "python3")
-
Check exit code and stderr:
result = await box.exec("command")
if result.exit_code != 0:
print(f"Failed: {result.stderr}")
Internal(String)
Internal BoxLite error.
Cause:
- Unexpected internal state
- I/O error
- JSON parsing error
- Unhandled edge case
Example:
Error: internal error: unexpected state transition
Solution:
- Enable debug logging:
RUST_LOG=debug python script.py
- Report issue with full logs to GitHub
- Include BoxLite version, platform, and reproduction steps
NotFound(String)
Box or resource not found.
Cause:
- Box ID does not exist
- Box was removed
- Image not in cache
Example:
Error: box not found: 01JJNH8...
Solution:
- List all boxes:
runtime.list()
- Verify box ID is correct
- Create new box if needed
AlreadyExists(String)
Box or resource already exists.
Cause:
- Duplicate box creation attempt
- Port already forwarded
Example:
Error: already exists: box with this ID exists
Solution:
- Use existing box:
runtime.get(box_id)
- Remove existing box:
box.remove()
- Use different configuration (e.g., different port)
InvalidState(String)
Box is in wrong state for requested operation.
Cause:
- Executing command on stopped box
- Stopping already stopped box
- Restarting box that never started
Example:
Error: invalid state: cannot execute on stopped box
Solution:
- Check box status:
info = await box.info(); print(info.status)
- Restart box if stopped:
runtime.get(box_id) (may auto-restart)
- Create new box if needed
Error Handling Patterns
Basic Error Handling
import boxlite
async def safe_execution():
try:
async with boxlite.SimpleBox(image="python:slim") as box:
result = await box.exec("python", "script.py")
# Check exit code
if result.exit_code != 0:
print(f"Command failed: {result.stderr}")
return
except Exception as e:
# All BoxLite errors are raised as Python exceptions
print(f"Error: {e}")
# Enable debug logging for details
# RUST_LOG=debug python script.py
Catching Specific Errors
from boxlite import SimpleBox, BoxliteError, ExecError, TimeoutError
async def handle_errors():
try:
async with SimpleBox(image="python:slim") as box:
result = await box.exec("python", "-c", "import nonexistent")
except ExecError as e:
print(f"Execution failed (exit {e.exit_code}): {e.stderr}")
except TimeoutError:
print("Operation timed out")
except BoxliteError as e:
print(f"BoxLite error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
Retry Pattern
import asyncio
from boxlite import SimpleBox, BoxliteError
async def with_retry(max_retries=3):
for attempt in range(max_retries):
try:
async with SimpleBox(image="python:slim") as box:
result = await box.exec("python", "-c", "print('success')")
return result.stdout
except BoxliteError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
await asyncio.sleep(wait_time)
else:
raise
Metrics
boxlite.RuntimeMetrics
Aggregate metrics across all boxes managed by a runtime instance.
| Field | Type | Description |
|---|
boxes_created | int | Total boxes created |
boxes_failed | int | Total boxes that failed |
running | int | Currently running boxes |
commands | int | Total command executions |
errors | int | Total errors |
runtime = Boxlite.default()
metrics = await runtime.metrics()
print(f"Boxes created: {metrics.boxes_created}")
print(f"Boxes failed: {metrics.boxes_failed}")
print(f"Running: {metrics.running}")
print(f"Commands: {metrics.commands}")
boxlite.BoxMetrics
Per-box resource usage metrics. Retrieved via box.metrics().
| Field | Type | Description |
|---|
commands | int | Total commands executed |
errors | int | Total errors |
total_create_ms | int | None | Time to create box in milliseconds |
guest_boot_ms | int | None | Guest boot time in milliseconds |
cpu | float | None | CPU usage |
mem | int | None | Memory usage in bytes |
metrics = await box.metrics()
print(f"Commands: {metrics.commands}")
print(f"Create time: {metrics.total_create_ms}ms")
print(f"Memory: {metrics.mem / (1024**2):.2f} MB")
box.metrics() is available on the low-level Box object (returned by runtime.create()). SimpleBox does not expose a metrics() method.
Monitoring Example
import asyncio
from boxlite import Boxlite, BoxOptions
async def monitor_box():
runtime = Boxlite.default()
box = await runtime.create(BoxOptions(image="python:slim"))
try:
# Start a workload
execution = await box.exec("python", ["-c", """
import time
data = []
for i in range(100):
data.append('x' * 10000)
time.sleep(0.1)
"""])
# Monitor metrics while running
for _ in range(10):
metrics = await box.metrics()
print(f"Commands: {metrics.commands} | "
f"Memory: {metrics.mem / (1024**2):.1f} MB")
await asyncio.sleep(1)
await execution.wait()
finally:
await box.stop()
Runtime-wide Monitoring
from boxlite import Boxlite, BoxOptions
async def runtime_monitoring():
runtime = Boxlite.default()
# Create several boxes
boxes = []
for i in range(3):
box = await runtime.create(BoxOptions(image="alpine:latest"))
boxes.append(box)
# Check runtime metrics
metrics = await runtime.metrics()
print(f"Running: {metrics.running}")
print(f"Total created: {metrics.boxes_created}")
# Clean up
for box in boxes:
await box.stop()
# Check again
metrics = await runtime.metrics()
print(f"Running: {metrics.running}")
See Also