forked from Arnab-Afk/monaco
Backend changes
This commit is contained in:
@@ -1,19 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/arnab-afk/monaco/handler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure logging with timestamps and file locations
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
log.Println("Starting Monaco code execution backend...")
|
||||
|
||||
h := handler.NewHandler()
|
||||
|
||||
http.HandleFunc("/submit", h.SubmitHandler)
|
||||
http.HandleFunc("/status", h.StatusHandler)
|
||||
http.HandleFunc("/result", h.ResultHandler)
|
||||
// Create a middleware for request logging
|
||||
loggingMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
startTime := time.Now()
|
||||
log.Printf("[HTTP] %s %s %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||||
next(w, r)
|
||||
log.Printf("[HTTP] %s %s completed in %v", r.Method, r.URL.Path, time.Since(startTime))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Server started at :8080")
|
||||
http.ListenAndServe(":8080", nil)
|
||||
// Register handlers with logging middleware
|
||||
http.HandleFunc("/submit", loggingMiddleware(h.SubmitHandler))
|
||||
http.HandleFunc("/status", loggingMiddleware(h.StatusHandler))
|
||||
http.HandleFunc("/result", loggingMiddleware(h.ResultHandler))
|
||||
|
||||
port := ":8080"
|
||||
log.Printf("Server started at %s", port)
|
||||
log.Fatal(http.ListenAndServe(port, nil))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// CodeSubmission represents a code submission for execution
|
||||
type CodeSubmission struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Language string `json:"language"` // Added language field
|
||||
Status string `json:"status"`
|
||||
Output string `json:"output"`
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Language string `json:"language"`
|
||||
Status string `json:"status"` // "queued", "running", "completed", "failed"
|
||||
QueuedAt time.Time `json:"queuedAt"`
|
||||
StartedAt time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt time.Time `json:"completedAt,omitempty"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Job represents a task that can be executed
|
||||
@@ -20,6 +22,7 @@ type JobQueue struct {
|
||||
|
||||
// NewJobQueue creates a new job queue with specified max concurrent workers
|
||||
func NewJobQueue(maxWorkers int) *JobQueue {
|
||||
log.Printf("[QUEUE] Initializing job queue with %d workers and buffer size 100", maxWorkers)
|
||||
jq := &JobQueue{
|
||||
jobs: make(chan Job, 100), // Buffer size of 100 jobs
|
||||
maxWorkers: maxWorkers,
|
||||
@@ -32,33 +35,52 @@ func NewJobQueue(maxWorkers int) *JobQueue {
|
||||
func (jq *JobQueue) start() {
|
||||
// Start the workers
|
||||
for i := 0; i < jq.maxWorkers; i++ {
|
||||
workerId := i + 1
|
||||
log.Printf("[WORKER-%d] Starting worker", workerId)
|
||||
jq.wg.Add(1)
|
||||
go func() {
|
||||
go func(id int) {
|
||||
defer jq.wg.Done()
|
||||
for job := range jq.jobs {
|
||||
jq.mu.Lock()
|
||||
jq.running++
|
||||
queueLen := len(jq.jobs)
|
||||
jq.mu.Unlock()
|
||||
|
||||
log.Printf("[WORKER-%d] Processing job (running: %d, queued: %d)",
|
||||
id, jq.running, queueLen)
|
||||
|
||||
startTime := time.Now()
|
||||
job.Execute()
|
||||
elapsed := time.Since(startTime)
|
||||
|
||||
jq.mu.Lock()
|
||||
jq.running--
|
||||
jq.mu.Unlock()
|
||||
|
||||
log.Printf("[WORKER-%d] Completed job in %v (running: %d, queued: %d)",
|
||||
id, elapsed, jq.running, len(jq.jobs))
|
||||
}
|
||||
}()
|
||||
log.Printf("[WORKER-%d] Worker shutting down", id)
|
||||
}(workerId)
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue adds a job to the queue
|
||||
func (jq *JobQueue) Enqueue(job Job) {
|
||||
jq.mu.Lock()
|
||||
queueLen := len(jq.jobs)
|
||||
jq.mu.Unlock()
|
||||
|
||||
log.Printf("[QUEUE] Job enqueued (queue length: %d, running: %d)", queueLen, jq.running)
|
||||
jq.jobs <- job
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down the job queue
|
||||
func (jq *JobQueue) Stop() {
|
||||
log.Println("[QUEUE] Stopping job queue, waiting for running jobs to complete")
|
||||
close(jq.jobs)
|
||||
jq.wg.Wait()
|
||||
log.Println("[QUEUE] Job queue shutdown complete")
|
||||
}
|
||||
|
||||
// QueueStats returns statistics about the queue
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -20,6 +21,7 @@ type ExecutionService struct {
|
||||
|
||||
// NewExecutionService creates a new execution service
|
||||
func NewExecutionService() *ExecutionService {
|
||||
log.Println("Initializing execution service with 3 concurrent workers")
|
||||
return &ExecutionService{
|
||||
queue: queue.NewJobQueue(3), // 3 concurrent executions max
|
||||
}
|
||||
@@ -41,29 +43,48 @@ func NewCodeExecutionJob(service *ExecutionService, submission *model.CodeSubmis
|
||||
|
||||
// Execute runs the code execution job
|
||||
func (j *CodeExecutionJob) Execute() {
|
||||
j.submission.Status = "running"
|
||||
j.service.executeLanguageSpecific(j.submission)
|
||||
submission := j.submission
|
||||
submission.Status = "running"
|
||||
submission.StartedAt = time.Now()
|
||||
|
||||
log.Printf("[JOB-%s] Starting execution for language: %s",
|
||||
submission.ID, submission.Language)
|
||||
|
||||
j.service.executeLanguageSpecific(submission)
|
||||
}
|
||||
|
||||
// ExecuteCode adds the submission to the execution queue
|
||||
func (s *ExecutionService) ExecuteCode(submission *model.CodeSubmission) {
|
||||
submission.Status = "queued"
|
||||
submission.QueuedAt = time.Now()
|
||||
|
||||
log.Printf("[SUBMISSION-%s] Code submission queued for language: %s (Queue length: %d)",
|
||||
submission.ID, submission.Language, s.queue.QueueStats()["queue_length"])
|
||||
|
||||
job := NewCodeExecutionJob(s, submission)
|
||||
s.queue.Enqueue(job)
|
||||
}
|
||||
|
||||
// executeLanguageSpecific runs code in the appropriate language container
|
||||
func (s *ExecutionService) executeLanguageSpecific(submission *model.CodeSubmission) {
|
||||
log.Printf("[EXEC-%s] Selecting execution environment for language: %s",
|
||||
submission.ID, submission.Language)
|
||||
|
||||
switch submission.Language {
|
||||
case "python":
|
||||
log.Printf("[EXEC-%s] Executing Python code", submission.ID)
|
||||
s.executePython(submission)
|
||||
case "java":
|
||||
log.Printf("[EXEC-%s] Executing Java code", submission.ID)
|
||||
s.executeJava(submission)
|
||||
case "c":
|
||||
log.Printf("[EXEC-%s] Executing C code", submission.ID)
|
||||
s.executeC(submission)
|
||||
case "cpp":
|
||||
log.Printf("[EXEC-%s] Executing C++ code", submission.ID)
|
||||
s.executeCpp(submission)
|
||||
default:
|
||||
log.Printf("[EXEC-%s] ERROR: Unsupported language: %s", submission.ID, submission.Language)
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Unsupported language: " + submission.Language
|
||||
}
|
||||
@@ -71,33 +92,49 @@ func (s *ExecutionService) executeLanguageSpecific(submission *model.CodeSubmiss
|
||||
|
||||
// GetQueueStats returns the current queue statistics
|
||||
func (s *ExecutionService) GetQueueStats() map[string]int {
|
||||
return s.queue.QueueStats()
|
||||
stats := s.queue.QueueStats()
|
||||
log.Printf("[QUEUE] Stats - Jobs in queue: %d, Running jobs: %d, Max workers: %d",
|
||||
stats["queue_length"], stats["running_jobs"], stats["max_workers"])
|
||||
return stats
|
||||
}
|
||||
|
||||
// Add a timeout function
|
||||
func (s *ExecutionService) executeWithTimeout(cmd *exec.Cmd, timeout time.Duration) ([]byte, error) {
|
||||
func (s *ExecutionService) executeWithTimeout(cmd *exec.Cmd, timeout time.Duration, submissionID string) ([]byte, error) {
|
||||
log.Printf("[TIMEOUT-%s] Setting execution timeout: %v", submissionID, timeout)
|
||||
|
||||
done := make(chan error, 1)
|
||||
var output []byte
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
log.Printf("[EXEC-%s] Starting command execution: %v", submissionID, cmd.Args)
|
||||
output, err = cmd.CombinedOutput()
|
||||
done <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
log.Printf("[TIMEOUT-%s] Execution timed out after %v seconds", submissionID, timeout.Seconds())
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Printf("[TIMEOUT-%s] Failed to kill process: %v", submissionID, err)
|
||||
return nil, fmt.Errorf("timeout reached but failed to kill process: %v", err)
|
||||
}
|
||||
return nil, fmt.Errorf("execution timed out after %v seconds", timeout.Seconds())
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
log.Printf("[EXEC-%s] Command execution failed: %v", submissionID, err)
|
||||
} else {
|
||||
log.Printf("[EXEC-%s] Command execution completed successfully", submissionID)
|
||||
}
|
||||
return output, err
|
||||
}
|
||||
}
|
||||
|
||||
// executePython runs Python code in a container
|
||||
func (s *ExecutionService) executePython(submission *model.CodeSubmission) {
|
||||
log.Printf("[PYTHON-%s] Preparing Python execution environment", submission.ID)
|
||||
startTime := time.Now()
|
||||
|
||||
cmd := exec.Command("docker", "run", "--rm", "-i",
|
||||
"--network=none", // No network access
|
||||
"--memory=100m", // Memory limit
|
||||
@@ -106,62 +143,84 @@ func (s *ExecutionService) executePython(submission *model.CodeSubmission) {
|
||||
"--ulimit", "nofile=64:64", // File descriptor limits
|
||||
"python:3.9", "python", "-c", submission.Code)
|
||||
|
||||
output, err := s.executeWithTimeout(cmd, 10*time.Second) // 10 second timeout
|
||||
log.Printf("[PYTHON-%s] Executing Python code with timeout: 10s", submission.ID)
|
||||
output, err := s.executeWithTimeout(cmd, 10*time.Second, submission.ID)
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
log.Printf("[PYTHON-%s] Python execution completed in %v", submission.ID, elapsed)
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
|
||||
// executeJava runs Java code in a container
|
||||
func (s *ExecutionService) executeJava(submission *model.CodeSubmission) {
|
||||
log.Printf("[JAVA-%s] Preparing Java execution environment", submission.ID)
|
||||
startTime := time.Now()
|
||||
|
||||
// Create temp directory for Java files
|
||||
tempDir, err := os.MkdirTemp("", "java-execution")
|
||||
if err != nil {
|
||||
log.Printf("[JAVA-%s] Failed to create temp directory: %v", submission.ID, err)
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to create temp directory: " + err.Error()
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
log.Printf("[JAVA-%s] Created temp directory: %s", submission.ID, tempDir)
|
||||
|
||||
// Write Java code to file
|
||||
javaFilePath := filepath.Join(tempDir, "Main.java")
|
||||
if err := os.WriteFile(javaFilePath, []byte(submission.Code), 0644); err != nil {
|
||||
log.Printf("[JAVA-%s] Failed to write Java file: %v", submission.ID, err)
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to write Java file: " + err.Error()
|
||||
return
|
||||
}
|
||||
log.Printf("[JAVA-%s] Wrote code to file: %s", submission.ID, javaFilePath)
|
||||
|
||||
// Run Java code in container
|
||||
cmd := exec.Command("docker", "run", "--rm",
|
||||
"--network=none", // No network access
|
||||
"--memory=200m", // Memory limit
|
||||
"--cpu-period=1000000", // CPU quota period
|
||||
"--cpu-quota=50000", // 10% CPU
|
||||
"--cpu-quota=50000", // 5% CPU
|
||||
"-v", tempDir+":/code", // Mount code directory
|
||||
"openjdk:11", "bash", "-c", "cd /code && javac Main.java && java Main")
|
||||
|
||||
output, err := s.executeWithTimeout(cmd, 1000*time.Second) // 10 second timeout
|
||||
log.Printf("[JAVA-%s] Executing Java code with timeout: 10s", submission.ID)
|
||||
output, err := s.executeWithTimeout(cmd, 10*time.Second, submission.ID)
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
log.Printf("[JAVA-%s] Java execution completed in %v", submission.ID, elapsed)
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
|
||||
// executeC runs C code in a container
|
||||
func (s *ExecutionService) executeC(submission *model.CodeSubmission) {
|
||||
log.Printf("[C-%s] Preparing C execution environment", submission.ID)
|
||||
startTime := time.Now()
|
||||
|
||||
// Create temp directory for C files
|
||||
tempDir, err := os.MkdirTemp("", "c-execution")
|
||||
if err != nil {
|
||||
log.Printf("[C-%s] Failed to create temp directory: %v", submission.ID, err)
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to create temp directory: " + err.Error()
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
log.Printf("[C-%s] Created temp directory: %s", submission.ID, tempDir)
|
||||
|
||||
// Write C code to file
|
||||
cFilePath := filepath.Join(tempDir, "main.c")
|
||||
if err := os.WriteFile(cFilePath, []byte(submission.Code), 0644); err != nil {
|
||||
log.Printf("[C-%s] Failed to write C file: %v", submission.ID, err)
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to write C file: " + err.Error()
|
||||
return
|
||||
}
|
||||
log.Printf("[C-%s] Wrote code to file: %s", submission.ID, cFilePath)
|
||||
|
||||
// Run C code in container
|
||||
cmd := exec.Command("docker", "run", "--rm",
|
||||
@@ -172,29 +231,40 @@ func (s *ExecutionService) executeC(submission *model.CodeSubmission) {
|
||||
"-v", tempDir+":/code", // Mount code directory
|
||||
"gcc:latest", "bash", "-c", "cd /code && gcc -o main main.c && ./main")
|
||||
|
||||
output, err := s.executeWithTimeout(cmd, 10*time.Second) // 10 second timeout
|
||||
log.Printf("[C-%s] Executing C code with timeout: 10s", submission.ID)
|
||||
output, err := s.executeWithTimeout(cmd, 50*time.Second, submission.ID)
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
log.Printf("[C-%s] C execution completed in %v", submission.ID, elapsed)
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
|
||||
// executeCpp runs C++ code in a container
|
||||
func (s *ExecutionService) executeCpp(submission *model.CodeSubmission) {
|
||||
log.Printf("[CPP-%s] Preparing C++ execution environment", submission.ID)
|
||||
startTime := time.Now()
|
||||
|
||||
// Create temp directory for C++ files
|
||||
tempDir, err := os.MkdirTemp("", "cpp-execution")
|
||||
if err != nil {
|
||||
log.Printf("[CPP-%s] Failed to create temp directory: %v", submission.ID, err)
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to create temp directory: " + err.Error()
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
log.Printf("[CPP-%s] Created temp directory: %s", submission.ID, tempDir)
|
||||
|
||||
// Write C++ code to file
|
||||
cppFilePath := filepath.Join(tempDir, "main.cpp")
|
||||
if err := os.WriteFile(cppFilePath, []byte(submission.Code), 0644); err != nil {
|
||||
log.Printf("[CPP-%s] Failed to write C++ file: %v", submission.ID, err)
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to write C++ file: " + err.Error()
|
||||
return
|
||||
}
|
||||
log.Printf("[CPP-%s] Wrote code to file: %s", submission.ID, cppFilePath)
|
||||
|
||||
// Run C++ code in container
|
||||
cmd := exec.Command("docker", "run", "--rm",
|
||||
@@ -205,7 +275,11 @@ func (s *ExecutionService) executeCpp(submission *model.CodeSubmission) {
|
||||
"-v", tempDir+":/code", // Mount code directory
|
||||
"gcc:latest", "bash", "-c", "cd /code && g++ -o main main.cpp && ./main")
|
||||
|
||||
output, err := s.executeWithTimeout(cmd, 10*time.Second) // 10 second timeout
|
||||
log.Printf("[CPP-%s] Executing C++ code with timeout: 10s", submission.ID)
|
||||
output, err := s.executeWithTimeout(cmd, 10*time.Second, submission.ID)
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
log.Printf("[CPP-%s] C++ execution completed in %v", submission.ID, elapsed)
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
@@ -215,11 +289,19 @@ func (s *ExecutionService) updateSubmissionResult(submission *model.CodeSubmissi
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
submission.CompletedAt = time.Now()
|
||||
executionTime := submission.CompletedAt.Sub(submission.StartedAt)
|
||||
totalTime := submission.CompletedAt.Sub(submission.QueuedAt)
|
||||
|
||||
if err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = string(output) + "\n" + err.Error()
|
||||
log.Printf("[RESULT-%s] Execution FAILED in %v (total time: %v, including queue: %v)",
|
||||
submission.ID, executionTime, totalTime, totalTime-executionTime)
|
||||
} else {
|
||||
submission.Status = "completed"
|
||||
submission.Output = string(output)
|
||||
log.Printf("[RESULT-%s] Execution COMPLETED in %v (total time: %v, including queue: %v)",
|
||||
submission.ID, executionTime, totalTime, totalTime-executionTime)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
Binary file not shown.
Reference in New Issue
Block a user