mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 11:25:49 +00:00
334 lines
12 KiB
Python
334 lines
12 KiB
Python
import docker
|
|
import tempfile
|
|
import os
|
|
import subprocess
|
|
import time
|
|
import uuid
|
|
import json
|
|
import threading
|
|
from typing import Dict, List, Any, Optional
|
|
from datetime import datetime
|
|
import queue
|
|
import signal
|
|
|
|
class RealCompilerService:
|
|
def __init__(self):
|
|
self.client = None # Lazy initialization
|
|
self.execution_queue = queue.Queue()
|
|
self.active_executions = {}
|
|
self.max_concurrent_executions = 5
|
|
self.docker_available = False
|
|
|
|
# Enhanced language configurations with real execution
|
|
self.language_configs = {
|
|
'python': {
|
|
'image': 'python:3.11-slim',
|
|
'file_ext': '.py',
|
|
'compile_command': None, # Python doesn't need compilation
|
|
'run_command': 'python /app/code.py',
|
|
'timeout': 30,
|
|
'memory_limit': '256m',
|
|
'cpu_limit': '0.5'
|
|
},
|
|
'java': {
|
|
'image': 'openjdk:17-alpine',
|
|
'file_ext': '.java',
|
|
'compile_command': 'javac /app/Main.java',
|
|
'run_command': 'java -cp /app Main',
|
|
'timeout': 30,
|
|
'memory_limit': '512m',
|
|
'cpu_limit': '0.5'
|
|
},
|
|
'cpp': {
|
|
'image': 'gcc:latest',
|
|
'file_ext': '.cpp',
|
|
'compile_command': 'g++ -o /app/program /app/code.cpp -std=c++17',
|
|
'run_command': '/app/program',
|
|
'timeout': 30,
|
|
'memory_limit': '256m',
|
|
'cpu_limit': '0.5'
|
|
},
|
|
'c': {
|
|
'image': 'gcc:latest',
|
|
'file_ext': '.c',
|
|
'compile_command': 'gcc -o /app/program /app/code.c',
|
|
'run_command': '/app/program',
|
|
'timeout': 30,
|
|
'memory_limit': '256m',
|
|
'cpu_limit': '0.5'
|
|
},
|
|
'javascript': {
|
|
'image': 'node:18-alpine',
|
|
'file_ext': '.js',
|
|
'compile_command': None,
|
|
'run_command': 'node /app/code.js',
|
|
'timeout': 30,
|
|
'memory_limit': '256m',
|
|
'cpu_limit': '0.5'
|
|
},
|
|
'bash': {
|
|
'image': 'bash:5.2-alpine3.18',
|
|
'file_ext': '.sh',
|
|
'compile_command': None,
|
|
'run_command': 'bash /app/code.sh',
|
|
'timeout': 30,
|
|
'memory_limit': '128m',
|
|
'cpu_limit': '0.3'
|
|
},
|
|
'go': {
|
|
'image': 'golang:1.21-alpine',
|
|
'file_ext': '.go',
|
|
'compile_command': 'go build -o /app/program /app/code.go',
|
|
'run_command': '/app/program',
|
|
'timeout': 30,
|
|
'memory_limit': '512m',
|
|
'cpu_limit': '0.5'
|
|
},
|
|
'rust': {
|
|
'image': 'rust:1.75-alpine',
|
|
'file_ext': '.rs',
|
|
'compile_command': 'rustc /app/code.rs -o /app/program',
|
|
'run_command': '/app/program',
|
|
'timeout': 60, # Rust compilation can be slow
|
|
'memory_limit': '1g',
|
|
'cpu_limit': '1.0'
|
|
}
|
|
}
|
|
|
|
# Start execution worker
|
|
self.start_execution_worker()
|
|
|
|
def _get_docker_client(self):
|
|
"""Lazily initialize Docker client"""
|
|
if self.client is None:
|
|
try:
|
|
self.client = docker.from_env()
|
|
self.docker_available = True
|
|
except Exception as e:
|
|
print(f"⚠️ Docker initialization failed: {e}")
|
|
self.docker_available = False
|
|
self.client = None
|
|
return self.client
|
|
|
|
def start_execution_worker(self):
|
|
"""Start background worker for code execution"""
|
|
def worker():
|
|
while True:
|
|
try:
|
|
execution_task = self.execution_queue.get(timeout=1)
|
|
self._execute_task(execution_task)
|
|
self.execution_queue.task_done()
|
|
except queue.Empty:
|
|
continue
|
|
except Exception as e:
|
|
print(f"Execution worker error: {e}")
|
|
|
|
worker_thread = threading.Thread(target=worker, daemon=True)
|
|
worker_thread.start()
|
|
|
|
def execute_code(self, code: str, language: str, input_data: str = "",
|
|
execution_id: str = None) -> Dict[str, Any]:
|
|
"""Execute code with real output capture"""
|
|
if language not in self.language_configs:
|
|
return {"error": f"Language '{language}' not supported"}
|
|
|
|
if not execution_id:
|
|
execution_id = str(uuid.uuid4())
|
|
|
|
config = self.language_configs[language]
|
|
|
|
try:
|
|
# Create execution context
|
|
execution_context = {
|
|
'execution_id': execution_id,
|
|
'code': code,
|
|
'language': language,
|
|
'input_data': input_data,
|
|
'config': config,
|
|
'start_time': datetime.now(),
|
|
'status': 'running'
|
|
}
|
|
|
|
self.active_executions[execution_id] = execution_context
|
|
|
|
# Execute in Docker container
|
|
result = self._execute_in_container(execution_context)
|
|
|
|
# Update execution context
|
|
execution_context['status'] = 'completed'
|
|
execution_context['end_time'] = datetime.now()
|
|
execution_context['result'] = result
|
|
|
|
return {
|
|
"success": True,
|
|
"execution_id": execution_id,
|
|
"output": result.get('output', ''),
|
|
"error": result.get('error', ''),
|
|
"execution_time": result.get('execution_time', 0),
|
|
"memory_used": result.get('memory_used', 0),
|
|
"exit_code": result.get('exit_code', 0),
|
|
"language": language,
|
|
"timestamp": datetime.now().isoformat()
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
"error": f"Execution failed: {str(e)}",
|
|
"execution_id": execution_id,
|
|
"language": language
|
|
}
|
|
finally:
|
|
# Clean up
|
|
if execution_id in self.active_executions:
|
|
del self.active_executions[execution_id]
|
|
|
|
def _execute_in_container(self, context: Dict) -> Dict[str, Any]:
|
|
"""Execute code in secure Docker container"""
|
|
code = context['code']
|
|
language = context['language']
|
|
input_data = context['input_data']
|
|
config = context['config']
|
|
|
|
# Check Docker availability
|
|
docker_client = self._get_docker_client()
|
|
if docker_client is None or not self.docker_available:
|
|
return {
|
|
"output": "",
|
|
"error": "Docker service is not available. Compiler service cannot execute code.",
|
|
"exit_code": -1,
|
|
"execution_time": 0,
|
|
"memory_used": 0
|
|
}
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
# Prepare code file
|
|
filename = f"code{config['file_ext']}" if language != 'java' else "Main.java"
|
|
file_path = os.path.join(temp_dir, filename)
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(code)
|
|
|
|
# Prepare input file
|
|
input_file = os.path.join(temp_dir, 'input.txt')
|
|
with open(input_file, 'w', encoding='utf-8') as f:
|
|
f.write(input_data)
|
|
|
|
try:
|
|
start_time = time.time()
|
|
|
|
# Create and run container
|
|
container = docker_client.containers.run(
|
|
config['image'],
|
|
command=self._build_execution_command(config, filename),
|
|
volumes={temp_dir: {'bind': '/app', 'mode': 'rw'}},
|
|
working_dir='/app',
|
|
mem_limit=config['memory_limit'],
|
|
cpu_period=100000,
|
|
cpu_quota=int(float(config['cpu_limit']) * 100000),
|
|
network_mode='none', # No network access
|
|
remove=True,
|
|
detach=False,
|
|
stdin_open=True,
|
|
tty=False,
|
|
timeout=config['timeout'],
|
|
# Security options
|
|
cap_drop=['ALL'],
|
|
security_opt=['no-new-privileges'],
|
|
read_only=False,
|
|
tmpfs={'/tmp': 'rw,noexec,nosuid,size=100m'}
|
|
)
|
|
|
|
execution_time = time.time() - start_time
|
|
output = container.decode('utf-8')
|
|
|
|
return {
|
|
"output": output.strip(),
|
|
"error": "",
|
|
"exit_code": 0,
|
|
"execution_time": round(execution_time, 3),
|
|
"memory_used": self._get_memory_usage(container)
|
|
}
|
|
|
|
except docker.errors.ContainerError as e:
|
|
return {
|
|
"output": "",
|
|
"error": f"Runtime error (exit code {e.exit_status}): {e.stderr.decode('utf-8') if e.stderr else 'Unknown error'}",
|
|
"exit_code": e.exit_status,
|
|
"execution_time": time.time() - start_time,
|
|
"memory_used": 0
|
|
}
|
|
except docker.errors.APIError as e:
|
|
return {
|
|
"output": "",
|
|
"error": f"Docker API error: {str(e)}",
|
|
"exit_code": -1,
|
|
"execution_time": 0,
|
|
"memory_used": 0
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"output": "",
|
|
"error": f"Execution error: {str(e)}",
|
|
"exit_code": -1,
|
|
"execution_time": 0,
|
|
"memory_used": 0
|
|
}
|
|
|
|
def _build_execution_command(self, config: Dict, filename: str) -> str:
|
|
"""Build the execution command for the container"""
|
|
commands = []
|
|
|
|
# Add compilation step if needed
|
|
if config.get('compile_command'):
|
|
commands.append(config['compile_command'])
|
|
|
|
# Add execution command with input redirection
|
|
run_cmd = config['run_command']
|
|
if '<' not in run_cmd: # Add input redirection if not present
|
|
run_cmd += ' < /app/input.txt 2>&1'
|
|
commands.append(run_cmd)
|
|
|
|
# Combine commands
|
|
return f"sh -c '{' && '.join(commands)}'"
|
|
|
|
def _get_memory_usage(self, container) -> int:
|
|
"""Get memory usage from container stats"""
|
|
try:
|
|
stats = container.stats(stream=False)
|
|
memory_usage = stats['memory']['usage']
|
|
return memory_usage
|
|
except:
|
|
return 0
|
|
|
|
def get_supported_languages(self) -> List[Dict[str, str]]:
|
|
"""Get list of supported languages with details"""
|
|
return [
|
|
{
|
|
'id': lang_id,
|
|
'name': lang_id.title(),
|
|
'extension': config['file_ext'],
|
|
'timeout': config['timeout'],
|
|
'memory_limit': config['memory_limit']
|
|
}
|
|
for lang_id, config in self.language_configs.items()
|
|
]
|
|
|
|
def get_execution_status(self, execution_id: str) -> Optional[Dict]:
|
|
"""Get status of a running execution"""
|
|
return self.active_executions.get(execution_id)
|
|
|
|
def cancel_execution(self, execution_id: str) -> bool:
|
|
"""Cancel a running execution"""
|
|
if execution_id in self.active_executions:
|
|
# Implementation would involve stopping the Docker container
|
|
del self.active_executions[execution_id]
|
|
return True
|
|
return False
|
|
|
|
# Create global instance
|
|
try:
|
|
real_compiler_service = RealCompilerService()
|
|
except Exception as e:
|
|
print(f"⚠️ Failed to initialize RealCompilerService: {e}")
|
|
real_compiler_service = RealCompilerService() # Still create instance for graceful fallback
|