13 KiB
Monaco Backend - Code Execution Service
Table of Contents
- Introduction
- Architecture
- Installation
- API Reference
- Code Execution
- Job Queue System
- Language Support
- Security Considerations
- Configuration
- Testing
- Performance Tuning
- Troubleshooting
Introduction
Monaco is a secure, containerized code execution backend service designed to run user-submitted code in multiple programming languages. It features a job queue system to manage execution resources, containerized execution environments for security, and a RESTful API for submission and monitoring.
Key Features:
- Multi-language support (Python, Java, C, C++)
- Secure containerized execution using Docker
- Resource limiting to prevent abuse
- Job queuing for managing concurrent executions
- Detailed execution statistics and monitoring
- Support for user input via stdin
- CORS support for browser-based clients
Architecture
Component Overview
Monaco follows a layered architecture with the following key components:
- HTTP Handlers (handler package) - Processes incoming HTTP requests
- Execution Service (service package) - Manages code execution in containers
- Job Queue (queue package) - Controls concurrent execution
- Data Models (model package) - Defines data structures
Request Flow
- Client submits code via
/submitendpoint - Request is validated and assigned a unique ID
- Submission is added to the job queue
- Worker picks job from queue when available
- Code is executed in appropriate Docker container
- Results are stored and available via
/resultendpoint
Dependency Diagram
Client Request → HTTP Handlers → Execution Service → Job Queue → Docker Containers
↑
Data Models
Installation
Prerequisites
- Go 1.22+
- Docker Engine
- Docker images for supported languages:
python:3.9eclipse-temurin:11-jdk-alpinegcc:latest
Setup Instructions
-
Clone the repository:
git clone https://github.com/arnab-afk/monaco.git cd monaco/backend -
Install Go dependencies:
go mod download -
Build the application:
go build -o monaco main.go -
Run the service:
./monaco
The service will start on port 8080 by default.
API Reference
Endpoints
POST /submit
Submits code for execution.
Request Body:
{
"language": "python", // Required: "python", "java", "c", or "cpp"
"code": "print('Hello, World!')", // Required: source code to execute
"input": "optional input string" // Optional: input to stdin
}
Response:
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1" // Unique ID for this submission
}
Status Codes:
- 202 Accepted - Code accepted for execution
- 400 Bad Request - Invalid request (e.g., unsupported language)
GET /status?id={submissionId}
Checks the status of a submission.
Response:
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed", // "pending", "queued", "running", "completed", "failed"
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z", // Only present if status is "running", "completed", or "failed"
"completedAt": "2025-03-25T14:30:02Z", // Only present if status is "completed" or "failed"
"executionTime": 1000 // Execution time in milliseconds (only if completed)
}
Status Codes:
- 200 OK - Status retrieved successfully
- 400 Bad Request - Missing ID parameter
- 404 Not Found - Submission with given ID not found
GET /result?id={submissionId}
Gets the execution result of a submission.
Response:
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed",
"language": "python",
"output": "Hello, World!",
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z",
"completedAt": "2025-03-25T14:30:02Z",
"executionTime": 1000,
"executionTimeFormatted": "1.0s",
"totalTime": 2000,
"totalTimeFormatted": "2.0s"
}
Status Codes:
- 200 OK - Result retrieved successfully
- 400 Bad Request - Missing ID parameter
- 404 Not Found - Submission with given ID not found
GET /queue-stats
Gets statistics about the job queue.
Response:
{
"queue_stats": {
"queue_length": 5,
"max_workers": 3,
"running_jobs": 3
},
"submissions": 42
}
Code Execution
Execution Process
- Language Detection: The system identifies the programming language of the submission.
- Environment Setup: A temporary directory is created for compiled languages.
- Container Setup: Docker containers are configured with resource limits.
- Compilation: For compiled languages (Java, C, C++), code is compiled first.
- Execution: The program is executed with the provided input.
- Resource Monitoring: Memory and CPU usage are limited during execution.
- Result Collection: Output and errors are captured and stored.
Language-Specific Processing
Python
- Directly executes Python code using the
-cflag - Uses
python:3.9Docker image - Resource limits: 100MB memory, 10% CPU
Java
- Detects class name using regex pattern matching
- Compiles with
javacand runs with optimized JVM settings - Uses
eclipse-temurin:11-jdk-alpineDocker image - Resource limits: 400MB memory, 50% CPU
- JVM flags:
-XX:+TieredCompilation,-XX:TieredStopAtLevel=1,-Xverify:none
C/C++
- Saves code to a file in a temporary directory
- Compiles with
gcc/g++and runs the executable - Uses
gcc:latestDocker image - Resource limits: 100MB memory, 10% CPU
Timeout Handling
All executions have a timeout limit:
- Python: 10 seconds
- Java: 15 seconds
- C/C++: 10 seconds
If execution exceeds this limit, the process is killed and an error is returned.
Job Queue System
Worker Pool
Monaco uses a worker pool to manage concurrent code executions:
- Default pool size: 20 workers (configurable)
- Maximum queue capacity: 100 jobs
- FIFO (First-In-First-Out) processing order
Job Lifecycle
- Creation: Job created when code is submitted
- Queuing: Job added to queue with
queuedstatus - Execution: Worker picks job from queue and changes status to
running - Completion: Job finishes with either
completedorfailedstatus
Performance Metrics
The queue tracks and reports:
- Current queue length
- Number of running jobs
- Maximum worker count
- Total number of submissions
Language Support
Python
- Version: Python 3.9
- Input Handling: Direct stdin piping
- Limitations: No file I/O, no package imports outside standard library
Java
- Version: Java 11 (Eclipse Temurin)
- Class Detection: Extracts class name from code using regex
- Memory Settings: 64MB min heap, 256MB max heap
- Best Practices: Use
public classwith the main method
C
- Version: Latest GCC
- Compilation Flags: Default GCC settings
- Execution: Compiled binary
C++
- Version: Latest G++
- Standard: C++17
- Execution: Compiled binary
Security Considerations
Containerization
All code execution happens within isolated Docker containers with:
- No network access (
--network=none) - Limited CPU and memory resources
- Limited file system access
- No persistent storage
Resource Limiting
- Memory Limits: 100-400MB depending on language
- CPU Limits: 10-50% of CPU depending on language
- File Descriptors: Limited to 64 for Python
- Execution Time: Enforced timeouts (10-15 seconds)
Known Limitations
- Container escape vulnerabilities
- Docker daemon security depends on host configuration
- Resource limits can be circumvented with certain techniques
Configuration
The service can be configured through environment variables:
PORT: HTTP port (default: 8080)MAX_WORKERS: Maximum concurrent executions (default: 3)QUEUE_SIZE: Maximum queue size (default: 100)DEFAULT_LANGUAGE: Default language if none specified (default: "python")
Testing
Unit Tests
Run unit tests with:
go test ./...
Monaco Backend Test Plan
Overview
This test plan outlines the testing approach for the Monaco code execution backend service.
Test Environment
- Development: Local workstations with Docker and Go
- Testing: Dedicated test server with Docker
- Production-like: Staging environment with similar resources to production
Test Types
Unit Tests
- Purpose: Verify individual components work as expected
- Components to Test:
- Handler package
- Queue package
- Execution service
- Models
- Tools: Go testing framework
Integration Tests
- Purpose: Verify components work together correctly
- Focus Areas:
- API endpoints
- End-to-end code execution flow
- Error handling
- Tools: Go testing framework, HTTP test utilities
Load Tests
- Purpose: Verify system performance under load
- Scenarios:
- Concurrent submissions
- Mixed language workloads
- Queue saturation
- Metrics:
- Request throughput
- Response times
- Success rates
- Resource utilization
- Tools: Custom Python test scripts
Test Data
- Simple programs in each language
- Programs with input requirements
- Programs with compile errors
- Programs with runtime errors
- Programs with timeouts
Test Execution
- Run unit tests on every code change
- Run integration tests before merging to main branch
- Run load tests weekly and before major releases
Success Criteria
- All unit tests pass
- Integration tests complete successfully
- Load tests show acceptable performance metrics:
- 95% of requests complete successfully
- 95th percentile response time < 5 seconds
- System can handle 20 concurrent users
Reporting
- Test results stored in CI/CD pipeline
- Performance metrics graphed over time
- Issues logged in GitHub issues
Load Testing
A Python script (test.py) is included for load testing:
python test.py
This script sends 500 requests concurrently and reports performance metrics.
Manual Testing with Curl
Python Example
curl -X POST http://localhost:8080/submit \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"code": "print(\"Hello, World!\")\nfor i in range(5):\n print(f\"Number: {i}\")",
"input": ""
}'
Java Example
curl -X POST http://localhost:8080/submit \
-H "Content-Type: application/json" \
-d '{
"language": "java",
"code": "public class Solution {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n for (int i = 0; i < 5; i++) {\n System.out.println(\"Number: \" + i);\n }\n }\n}",
"input": ""
}'
Performance Tuning
Optimizing Worker Count
The optimal worker count depends on:
- CPU cores available
- Memory available
- Docker container startup time
For most single-server deployments, 3-5 workers is optimal.
Memory Considerations
Each language has different memory requirements:
- Python: ~50-100MB per instance
- Java: ~200-400MB per instance
- C/C++: ~50-100MB per instance
Calculate total memory needs as: (Python instances × 100MB) + (Java instances × 400MB) + (C/C++ instances × 100MB)
Disk Space Management
Temporary files are cleaned up after execution, but with high request volumes, ensure adequate disk space for concurrent operations (approximately 1-5MB per request for compiled languages).
Troubleshooting
Common Issues
Docker Connection Errors
Error: Cannot connect to the Docker daemon
Solution: Ensure Docker daemon is running with systemctl start docker or docker --version
Permissions Issues
Error: Permission denied while trying to connect to the Docker daemon socket
Solution: Add user to docker group: sudo usermod -aG docker $USER
Container Resource Limits
Error: Container killed due to memory limit
Solution: Increase memory limits in execution service or optimize submitted code
File Not Found Errors
Error: Failed to write Java file
Solution: Check temporary directory permissions and disk space
Logs
The service provides structured logs with prefixes for easier filtering:
[HTTP]- API requests[QUEUE]- Queue operations[WORKER-n]- Worker activities[EXEC-id]- Execution details[PYTHON/JAVA/C/CPP-id]- Language-specific logs[TIMEOUT-id]- Timeout events[RESULT-id]- Execution results
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.