Files
wehub-resource-sync 5a558eb09e
Backend Tests / discover-tests (push) Waiting to run
Backend Tests / ${{ matrix.name }} (push) Blocked by required conditions
Build and Publish SDK / build-and-publish (push) Waiting to run
Build Opik Docker Images / set-version (push) Waiting to run
Build Opik Docker Images / build-backend (push) Blocked by required conditions
Build Opik Docker Images / build-sandbox-executor-python (push) Blocked by required conditions
Build Opik Docker Images / build-python-backend (push) Blocked by required conditions
Build Opik Docker Images / build-frontend (push) Blocked by required conditions
Build Opik Docker Images / create-git-tag (push) Blocked by required conditions
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Waiting to run
Docs - Publish / run (push) Waiting to run
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Waiting to run
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Blocked by required conditions
Frontend Unit Tests / Test on Node 20 (push) Waiting to run
Guardrails E2E Tests / Select Python version matrix (push) Waiting to run
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Blocked by required conditions
Guardrails E2E Tests / 📢 Slack Notification (push) Blocked by required conditions
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Waiting to run
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Blocked by required conditions
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Waiting to run
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Waiting to run
Lint Opik Helm Chart / unittest-helm-chart (push) Waiting to run
Lint Opik Helm Chart / render-equality (push) Waiting to run
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Waiting to run
Python BE E2E Tests / Python BE E2E (push) Waiting to run
Python Backend Tests / run-python-backend-tests (push) Waiting to run
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
SDK E2E Libraries Integration Tests / Check Secrets (push) Waiting to run
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / build-opik (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Blocked by required conditions
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Waiting to run
TypeScript SDK Build & Publish / build-and-publish (push) Waiting to run
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Waiting to run
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

24 KiB

IsolatedSubprocessExecutor - Complete Documentation

Status: Production Ready | Version: 2.1 | Last Updated: October 22, 2025 Note: This is the authoritative merged documentation combining all implementation details and quick references. Latest: Per-process log collectors support full concurrent execution with zero log loss guarantee.

📋 Table of Contents

  1. Executive Summary
  2. Quick Start
  3. Core Architecture
  4. Implementation Details
  5. Concurrent Execution
  6. Configuration Reference
  7. Usage Patterns
  8. Lifecycle Management
  9. Logging & Monitoring
  10. Production Checklist
  11. Troubleshooting & FAQ

Executive Summary

Problem Solved

ProcessExecutor maintains a reusable worker pool with a shared environment, causing potential environment variable leakage between concurrent executions.

IsolatedSubprocessExecutor creates fresh subprocesses for each execution with completely isolated and scoped environment variables - no leakage, no conflicts, safe for multi-tenant systems.

Key Features

Environment Variable Isolation - Each execution has scoped, isolated environment variables
Subprocess Lifecycle Management - Automatic creation and cleanup
Teardown Callbacks - Register cleanup functions to be called during teardown
Context Manager Support - Use with statement for automatic resource cleanup
Thread-Safe Concurrent Execution - Safe to use with ThreadPoolExecutor and AsyncIO
Resource Limiting - Stack memory limited to 20MB per subprocess
Log Streaming - Optional HTTP-based log collection to backend
OpenTelemetry Metrics - Creation and execution latency tracking
Comprehensive Error Handling - All error paths handled gracefully
Timeout Support - Prevent runaway executions
Zero Shared State - Completely independent executions

Use Cases

Perfect For Not For
Multi-tenant systems Extreme high throughput (>100/sec)
Different configs per execution Real-time streaming (<10ms latency)
Environment variable isolation Resource-constrained environments
Security-sensitive operations
Different API keys per execution

Performance Profile

Metric Value
Throughput 5-10 executions/second
Per-execution Overhead ~150ms (subprocess creation)
Memory per Subprocess ~20MB stack limit
Thread Safe Yes
Concurrent Safe Yes
Auto Cleanup Yes

Quick Start

60-Second Integration

1. Import

from opik_backend.executor_isolated import IsolatedSubprocessExecutor

2. Create Instance

executor = IsolatedSubprocessExecutor(timeout_secs=30)

3. Create Python File to Execute

# metric.py
import json
from opik.evaluation.metrics import base_metric, score_result

result = {
    "scores": [{
        "value": 0.95,
        "name": "my_metric",
        "reason": "Works!"
    }]
}
print(json.dumps(result))

4. Execute File

result = executor.execute(file_path="/path/to/metric.py", data={})
# Output: {"scores": [{"value": 0.95, "name": "my_metric", "reason": "Works!"}]}

5. With Environment Variables

env_vars = {
    "TENANT_ID": "tenant_123",
    "API_KEY": "secret_key",
}

result = executor.execute(
    file_path="/path/to/metric.py",
    data={},
    env_vars=env_vars
)
# Environment variables are isolated to this execution

6. Context Manager (Automatic Cleanup)

with IsolatedSubprocessExecutor() as executor:
    result = executor.execute(file_path="/path/to/metric.py", data={})
    # Automatic teardown when exiting the context

Core Architecture

File Structure

1. executor_isolated.py - Main Executor Class

Location: apps/opik-python-backend/src/opik_backend/executor_isolated.py

Responsibilities:

  • Creates isolated subprocesses for each code execution
  • Passes data via JSON over stdin/stdout
  • Scopes environment variables per execution
  • Enforces 20MB stack memory limit
  • Provides process lifecycle management (kill, teardown callbacks)
  • Integrates with BatchLogCollector for optional log streaming

Key Methods:

# Execute code with isolated environment
result = executor.execute(
    code="...",
    data={...},
    env_vars={...},
    optimization_id="opt-123",
    job_id="job-456"
)

# Register callbacks for cleanup
executor.register_teardown_callback(cleanup_func)

# Manual process management
executor.kill_process(pid)
executor.kill_all_processes()
executor.teardown()

# Context manager support
with executor:
    result = executor.execute(...)

2. subprocess_logger.py - Log Collection & Streaming

Location: apps/opik-python-backend/src/opik_backend/subprocess_logger.py

Key Classes:

  • SubprocessLogRecord: Represents a single log entry with timestamp, level, message, attributes
  • BatchLogCollector: Collects, batches, and sends logs via HTTP

Features:

  • Captures stdout/stderr from subprocesses
  • Parses JSON-formatted logs with fallback to plain text
  • Batches logs by time (1 second default) or size (10MB default)
  • Sends via HTTP POST with gzip compression support
  • Includes authentication headers (Authorization, Comet-Workspace)
  • Thread-safe with background flush thread
  • Graceful error handling (logs warnings, doesn't crash)

Usage:

logger = BatchLogCollector(
    backend_url="http://api.example.com/logs",
    optimization_id="opt-123",
    job_id="job-456",
    api_key="secret-key",
    workspace="workspace-id"
)

# Process logs from subprocess
logger.process_subprocess_output(stdout, stderr)

3. subprocess_log_config.py - Centralized Configuration

Location: apps/opik-python-backend/src/opik_backend/subprocess_log_config.py

Responsibilities:

  • Centralized environment variable reading (single source of truth)
  • Configuration validation and defaults
  • No side effects (only getenv calls)

Methods:

  • get_backend_url() - Log backend HTTP endpoint
  • is_enabled() - Check if logging is enabled
  • get_flush_interval_ms() - Time-based flush interval
  • get_max_size_bytes() - Size-based flush threshold
  • get_request_timeout_secs() - HTTP request timeout
  • should_fail_on_missing_backend() - Error handling mode
  • is_fully_configured() - All required config present

Architecture Diagram

┌─────────────────────────────────────────────────────────────┐
│  Parent Process (IsolatedSubprocessExecutor)                 │
│                                                              │
│  ┌─────────────────────────────────────────────────────────┐│
│  │ execute(code, data, env_vars, ...)                      ││
│  └──────────────────┬──────────────────────────────────────┘│
│                     │                                        │
│        ┌────────────┴────────────┐                          │
│        │                         │                          │
│    ┌───▼──────┐          ┌───────▼────────┐                │
│    │ Load     │          │ Prepare        │                │
│    │ Code     │          │ Environment    │                │
│    └───┬──────┘          └────────────────┘                │
│        │                                                    │
│        └──────────────┬──────────────────┐                 │
│                       │                  │                 │
│              ┌────────▼────────┐  ┌──────▼──────┐          │
│              │ Create Wrapper  │  │ json.dumps  │          │
│              │ Script          │  │ Input data  │          │
│              └────────┬────────┘  └──────┬──────┘          │
│                       │                  │                 │
│                   ┌───▼──────────────────▼───┐             │
│                   │ subprocess.Popen()        │             │
│                   │ python -c <wrapper>       │             │
│                   └───┬──────────────────┬───┘             │
│                       │                  │                 │
└───────────────────────┼──────────────────┼─────────────────┘
                        │                  │
        ┌───────────────▼────────────────▼─────────────────┐
        │  Child Process (Subprocess)                      │
        │                                                  │
        │  stdin: ◄── json data                            │
        │  Read JSON input                                 │
        │  exec(user_code)                                 │
        │  print(json.dumps(result)) to stdout ──► stdout  │
        │  Logger output ──────────────────────► stderr    │
        └──────────────────────────────────────────────────┘
                        │                  │
        ┌───────────────▼────────────────▼─────────────────┐
        │  Parent Process Continues                        │
        │                                                  │
        │  communicate() retrieves stdout/stderr           │
        │                                                  │
        │  ┌────────────────────────────────────────────┐  │
        │  │ if logging enabled:                        │  │
        │  │   BatchLogCollector.process_subprocess()   │  │
        │  │     - Parse logs from stderr/stdout        │  │
        │  │     - Batch by time/size                   │  │
        │  │     - POST to backend with gzip            │  │
        │  └────────────────────────────────────────────┘  │
        │                                                  │
        │  Parse result JSON from last stdout line         │
        │  Return result to caller                         │
        └──────────────────────────────────────────────────┘

Implementation Details

Code Execution Flow

1. Input Preparation

# User code
code = """
from opik.evaluation.metrics import base_metric, score_result
result = {"scores": [{"value": 0.8, "name": "quality"}]}
print(json.dumps(result))
"""

# Data to pass to code
data = {"text": "Hello world"}

# Environment variables (scoped to subprocess)
env_vars = {"CUSTOM_VAR": "value", "OPIK_API_KEY": "key", "OPIK_WORKSPACE": "ws"}

2. Wrapper Script Creation

# IsolatedSubprocessExecutor creates wrapper code internally
wrapper_code = """
import json
import sys

input_data = json.loads(sys.stdin.read())
data = input_data["data"]
payload_type = input_data["payload_type"]

# User's code here (injected)
result = {"scores": [{"value": 0.8, "name": "quality"}]}
print(json.dumps(result))
"""

3. Subprocess Execution

python -c '<wrapper_code>'
# stdin: {"data": {"text": "Hello"}, "payload_type": null}
# stdout: {"scores": [{"value": 0.8, "name": "quality"}]}
# stderr: any logs from the code

4. Log Collection (Optional)

# If logging enabled:
if SubprocessLogConfig.is_enabled():
    mylogger = BatchLogCollector(
        backend_url="http://api.example.com/logs",
        optimization_id="opt-123",
        job_id="job-456",
        api_key=env_vars.get("OPIK_API_KEY", ""),
        workspace=env_vars.get("OPIK_WORKSPACE", ""),
    )
    mylogger.process_subprocess_output(stdout, stderr)
    # Sends: POST with {logs: [...], optimization_id, job_id}

Resource Limiting

Memory Limiting

  • Limit Type: Stack memory only (RLIMIT_STACK)
  • Limit Size: 20MB per subprocess
  • Effect: Prevents infinite recursion and stack overflow
  • Doesn't Affect: Heap allocations, runtime data structures
  • Rationale: Matches ProcessExecutor behavior, allows normal operations

Concurrent Execution

Per-Process Log Collectors

Architecture

Each subprocess gets its own independent log collector:

# Internal structure
_log_collectors = {
    1234: BatchLogCollector(...),  # Process 1 logs
    1235: BatchLogCollector(...),  # Process 2 logs
    1236: BatchLogCollector(...),  # Process 3 logs
}

Benefits

Full Concurrent Support: Multiple processes can run simultaneously
Independent Log Streaming: Each process streams logs independently
Zero Interference: Closing one process's logs doesn't affect others
Thread-Safe: Protected with locks during add/remove operations
Zero Log Loss: Proper shutdown sequence: signal → flush → cleanup

Concurrent Execution Flow

import concurrent.futures

executor = IsolatedSubprocessExecutor()

def execute_with_tenant(tenant_id):
    return executor.execute(
        file_path="/path/to/metric.py",
        data={"tenant_id": tenant_id},
        env_vars={"TENANT_ID": tenant_id},
        optimization_id=f"opt_{tenant_id}",
        job_id=f"job_{tenant_id}",
    )

# Run 10 concurrent executions
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
    futures = [pool.submit(execute_with_tenant, f"tenant_{i}") for i in range(10)]
    results = [f.result() for f in concurrent.futures.as_completed(futures)]

Thread Safety Guarantees

Operation Thread-Safe Protected By
execute() Yes Process isolation
kill_process() Yes _process_lock
_log_collectors access Yes _process_lock
Log streaming Yes ThreadPoolExecutor (single-threaded)
Shutdown Yes Signal → Executor.shutdown(wait=True) → Final flush

Shutdown Sequence

Executor with 3 concurrent processes:

├─ Process A (PID 1000)
│  └─ _log_collectors[1000] → streams logs
├─ Process B (PID 1001)  
│  └─ _log_collectors[1001] → streams logs
└─ Process C (PID 1002)
   └─ _log_collectors[1002] → streams logs

On teardown():
├─ Signal all processes to terminate
├─ Wait for all to exit
├─ For each process:
│  ├─ Signal stop (should_stop = True)
│  ├─ Shutdown executor (wait for pending flushes)
│  ├─ Final flush (all logs sent)
│  └─ Cleanup threads
└─ All logs captured, zero loss guarantee ✓

Configuration Reference

Environment Variables

All configuration via SubprocessLogConfig reads from environment variables:

# Logging Backend Configuration
SUBPROCESS_LOG_ENABLED=true/false              # Enable logging (default: false)
OPIK_SUBPROCESS_LOG_BACKEND_URL=...            # Log backend HTTP endpoint
SUBPROCESS_LOG_FLUSH_INTERVAL=1000             # Flush interval in ms (default: 1000)
SUBPROCESS_LOG_MAX_SIZE=10485760               # Max buffer size in bytes (default: 10MB)
SUBPROCESS_LOG_REQUEST_TIMEOUT=60              # HTTP request timeout in seconds (default: 60)
SUBPROCESS_LOG_FAIL_ON_MISSING_BACKEND=false   # Fail if backend URL missing (default: false)

Logging Credentials (via env_vars parameter)

These are passed via the env_vars parameter to execute(), not via environment variables:

executor.execute(
    code=code,
    data=data,
    env_vars={
        "OPIK_API_KEY": "your-api-key",      # Used for Authorization header
        "OPIK_WORKSPACE": "workspace-id",     # Used for Comet-Workspace header
    }
)

Error Handling Modes

Graceful Mode (Default)

SUBPROCESS_LOG_FAIL_ON_MISSING_BACKEND=false
  • If backend_url not configured: Logs warning, skips logging, continues execution
  • Execution succeeds even if logging fails

Strict Mode

SUBPROCESS_LOG_FAIL_ON_MISSING_BACKEND=true
  • If backend_url not configured: Raises ValueError
  • Execution fails with clear error message

Usage Patterns

Pattern 1: Multi-Tenant Scoring

executor = IsolatedSubprocessExecutor()

for tenant in tenants:
    result = executor.execute(
        code,
        data,
        env_vars={
            "TENANT_ID": tenant.id,
            "OPIK_API_KEY": tenant.api_key,
            "OPIK_WORKSPACE": tenant.workspace,
        },
        optimization_id=f"opt_{tenant.id}",
        job_id=f"job_{tenant.id}",
    )
    process_result(result)

Pattern 2: Concurrent Execution

import concurrent.futures

executor = IsolatedSubprocessExecutor()

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
    futures = [
        pool.submit(
            executor.execute,
            code,
            data,
            {"TENANT_ID": f"tenant_{i}"}
        )
        for i in range(10)
    ]
    results = [f.result() for f in concurrent.futures.as_completed(futures)]

Pattern 3: Context Manager (Auto Cleanup)

with IsolatedSubprocessExecutor(timeout_secs=30) as executor:
    result = executor.execute(code, data, env_vars)
    # Automatic teardown when exiting context

Pattern 4: With Logging

import os

# Configure logging
os.environ["SUBPROCESS_LOG_ENABLED"] = "true"
os.environ["OPIK_SUBPROCESS_LOG_BACKEND_URL"] = "http://api.example.com/logs"
os.environ["SUBPROCESS_LOG_FLUSH_INTERVAL"] = "500"  # 500ms
os.environ["SUBPROCESS_LOG_MAX_SIZE"] = str(5 * 1024 * 1024)  # 5MB

executor = IsolatedSubprocessExecutor()

result = executor.execute(
    code=code,
    data=data,
    env_vars={"OPIK_API_KEY": "key", "OPIK_WORKSPACE": "ws"},
    optimization_id="opt-123",
    job_id="job-456",
)
# Logs are automatically sent to backend

Lifecycle Management

Context Manager Pattern

with IsolatedSubprocessExecutor() as executor:
    # Setup
    executor.register_teardown_callback(lambda: print("Cleanup 1"))
    executor.register_teardown_callback(lambda: print("Cleanup 2"))
    
    # Execute
    result = executor.execute(code, data)
    
    # Automatic teardown on exit
    # Teardown callbacks are called in reverse order

Manual Lifecycle

executor = IsolatedSubprocessExecutor()

# Register teardown callbacks
def cleanup():
    print("Cleaning up...")

executor.register_teardown_callback(cleanup)

# Execute
result = executor.execute(code, data)

# Manual teardown
executor.teardown()
# All teardown callbacks called

Process Killing

executor = IsolatedSubprocessExecutor()

# Kill specific process
executor.kill_process(pid, timeout=2)

# Kill all active processes
executor.kill_all_processes()

Logging & Monitoring

Log Structure

Log Entry Format

{
    "timestamp": 1697539200000,
    "level": "INFO",
    "logger_name": "task",
    "message": "Task started",
    "attributes": {"step": 1}
}

Supported Log Sources

  1. Python logging module - JSON-formatted logs to stderr
  2. Print to stdout - Plain text lines
  3. Print to stderr - Plain text lines
  4. JSON to stdout/stderr - Structured logs

Log Batching

  • Time-based: Flush every 1 second (configurable)
  • Size-based: Flush when buffer reaches 10MB (configurable)
  • Event-based: Flush on shutdown

HTTP Request

POST /logs HTTP/1.1
Content-Type: application/json
Authorization: <api_key>
Comet-Workspace: <workspace>
Content-Encoding: gzip

{
    "optimization_id": "opt-123",
    "job_id": "job-456",
    "logs": [
        {"timestamp": ..., "level": "INFO", "message": "..."},
        ...
    ]
}

OpenTelemetry Metrics

# Available metrics (via OpenTelemetry):
- isolated_subprocess_creation_latency     # Subprocess creation time (ms)
- isolated_subprocess_execution_latency    # Code execution time (ms)
- isolated_subprocess_active_count         # Current active subprocesses

Production Checklist

  • No mutable default arguments
  • No silent failures (explicit error handling)
  • Proper error logging throughout
  • Thread-safe operations with locks
  • Per-process log collectors (dictionary mapping PID → BatchLogCollector)
  • Full concurrent execution support (tested with 3 parallel processes)
  • Zero log loss guarantee (signal → flush → cleanup sequence)
  • Graceful degradation on config errors
  • Resource limits enforced (20MB stack)
  • Comprehensive test coverage (23 tests, 100% pass)
  • Clear configuration interface
  • Background thread cleanup with ThreadPoolExecutor
  • Memory-efficient log batching
  • Automatic process cleanup
  • Timeout handling
  • JSON-based IPC
  • OpenTelemetry integration
  • Context manager support with automatic teardown

Troubleshooting & FAQ

Common Issues

Issue: "Subprocess logging enabled but backend_url not configured"

Cause: SUBPROCESS_LOG_ENABLED=true but OPIK_SUBPROCESS_LOG_BACKEND_URL not set

Solution:

# Either disable logging
export SUBPROCESS_LOG_ENABLED=false

# Or set the backend URL
export OPIK_SUBPROCESS_LOG_BACKEND_URL=http://api.example.com/logs

Issue: "requests library not available for log posting"

Cause: requests library not installed

Solution:

pip install requests

Issue: Subprocess timeout

Cause: Code execution takes longer than timeout

Solution:

# Increase timeout
executor = IsolatedSubprocessExecutor(timeout_secs=60)

FAQ

Q: Can I share state between executions?
A: No, each execution is completely isolated. This is by design.

Q: What happens to environment variables in the subprocess?
A: They are isolated to that execution only. Parent process not affected.

Q: Can I modify the code being executed?
A: Yes, the code parameter accepts both file paths and inline code strings.

Q: Is it thread-safe?
A: Yes, fully thread-safe. Multiple threads can call execute() concurrently.

Q: What's the memory limit?
A: 20MB stack memory per subprocess (prevents infinite recursion).

Q: Can I access files from the subprocess?
A: Yes, the subprocess has access to the filesystem (OS-level resources are shared).


Files Reference

File Purpose Location
executor_isolated.py Main executor class src/opik_backend/
subprocess_logger.py Log collection & HTTP streaming src/opik_backend/
subprocess_log_config.py Configuration management src/opik_backend/
test_executor_isolated.py Executor unit tests (17 tests) tests/
test_subprocess_logging.py Logging integration tests (4 tests) tests/

Last Updated: October 22, 2025
Status: Production Ready
Version: 2.1
Test Coverage: 23 tests, 100% passing
Key Feature: Per-process log collectors with zero log loss guarantee for concurrent execution