Backend initial Commit
This commit is contained in:
225
backend/service/execution.go
Normal file
225
backend/service/execution.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/arnab-afk/monaco/model"
|
||||
"github.com/arnab-afk/monaco/queue"
|
||||
)
|
||||
|
||||
// ExecutionService handles code execution for multiple languages
|
||||
type ExecutionService struct {
|
||||
mu sync.Mutex
|
||||
queue *queue.JobQueue
|
||||
}
|
||||
|
||||
// NewExecutionService creates a new execution service
|
||||
func NewExecutionService() *ExecutionService {
|
||||
return &ExecutionService{
|
||||
queue: queue.NewJobQueue(3), // 3 concurrent executions max
|
||||
}
|
||||
}
|
||||
|
||||
// CodeExecutionJob represents a job to execute code
|
||||
type CodeExecutionJob struct {
|
||||
service *ExecutionService
|
||||
submission *model.CodeSubmission
|
||||
}
|
||||
|
||||
// NewCodeExecutionJob creates a new code execution job
|
||||
func NewCodeExecutionJob(service *ExecutionService, submission *model.CodeSubmission) *CodeExecutionJob {
|
||||
return &CodeExecutionJob{
|
||||
service: service,
|
||||
submission: submission,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute runs the code execution job
|
||||
func (j *CodeExecutionJob) Execute() {
|
||||
j.submission.Status = "running"
|
||||
j.service.executeLanguageSpecific(j.submission)
|
||||
}
|
||||
|
||||
// ExecuteCode adds the submission to the execution queue
|
||||
func (s *ExecutionService) ExecuteCode(submission *model.CodeSubmission) {
|
||||
submission.Status = "queued"
|
||||
job := NewCodeExecutionJob(s, submission)
|
||||
s.queue.Enqueue(job)
|
||||
}
|
||||
|
||||
// executeLanguageSpecific runs code in the appropriate language container
|
||||
func (s *ExecutionService) executeLanguageSpecific(submission *model.CodeSubmission) {
|
||||
switch submission.Language {
|
||||
case "python":
|
||||
s.executePython(submission)
|
||||
case "java":
|
||||
s.executeJava(submission)
|
||||
case "c":
|
||||
s.executeC(submission)
|
||||
case "cpp":
|
||||
s.executeCpp(submission)
|
||||
default:
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Unsupported language: " + submission.Language
|
||||
}
|
||||
}
|
||||
|
||||
// GetQueueStats returns the current queue statistics
|
||||
func (s *ExecutionService) GetQueueStats() map[string]int {
|
||||
return s.queue.QueueStats()
|
||||
}
|
||||
|
||||
// Add a timeout function
|
||||
func (s *ExecutionService) executeWithTimeout(cmd *exec.Cmd, timeout time.Duration) ([]byte, error) {
|
||||
done := make(chan error, 1)
|
||||
var output []byte
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
output, err = cmd.CombinedOutput()
|
||||
done <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
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:
|
||||
return output, err
|
||||
}
|
||||
}
|
||||
|
||||
// executePython runs Python code in a container
|
||||
func (s *ExecutionService) executePython(submission *model.CodeSubmission) {
|
||||
cmd := exec.Command("docker", "run", "--rm", "-i",
|
||||
"--network=none", // No network access
|
||||
"--memory=100m", // Memory limit
|
||||
"--cpu-period=100000", // CPU quota period
|
||||
"--cpu-quota=10000", // 10% CPU
|
||||
"--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
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
|
||||
// executeJava runs Java code in a container
|
||||
func (s *ExecutionService) executeJava(submission *model.CodeSubmission) {
|
||||
// Create temp directory for Java files
|
||||
tempDir, err := os.MkdirTemp("", "java-execution")
|
||||
if err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to create temp directory: " + err.Error()
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Write Java code to file
|
||||
javaFilePath := filepath.Join(tempDir, "Main.java")
|
||||
if err := os.WriteFile(javaFilePath, []byte(submission.Code), 0644); err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to write Java file: " + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
"-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
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
|
||||
// executeC runs C code in a container
|
||||
func (s *ExecutionService) executeC(submission *model.CodeSubmission) {
|
||||
// Create temp directory for C files
|
||||
tempDir, err := os.MkdirTemp("", "c-execution")
|
||||
if err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to create temp directory: " + err.Error()
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Write C code to file
|
||||
cFilePath := filepath.Join(tempDir, "main.c")
|
||||
if err := os.WriteFile(cFilePath, []byte(submission.Code), 0644); err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to write C file: " + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
// Run C code in container
|
||||
cmd := exec.Command("docker", "run", "--rm",
|
||||
"--network=none", // No network access
|
||||
"--memory=100m", // Memory limit
|
||||
"--cpu-period=100000", // CPU quota period
|
||||
"--cpu-quota=10000", // 10% CPU
|
||||
"-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
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
|
||||
// executeCpp runs C++ code in a container
|
||||
func (s *ExecutionService) executeCpp(submission *model.CodeSubmission) {
|
||||
// Create temp directory for C++ files
|
||||
tempDir, err := os.MkdirTemp("", "cpp-execution")
|
||||
if err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to create temp directory: " + err.Error()
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Write C++ code to file
|
||||
cppFilePath := filepath.Join(tempDir, "main.cpp")
|
||||
if err := os.WriteFile(cppFilePath, []byte(submission.Code), 0644); err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = "Failed to write C++ file: " + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
// Run C++ code in container
|
||||
cmd := exec.Command("docker", "run", "--rm",
|
||||
"--network=none", // No network access
|
||||
"--memory=100m", // Memory limit
|
||||
"--cpu-period=100000", // CPU quota period
|
||||
"--cpu-quota=10000", // 10% CPU
|
||||
"-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
|
||||
|
||||
s.updateSubmissionResult(submission, output, err)
|
||||
}
|
||||
|
||||
// updateSubmissionResult updates the submission with execution results
|
||||
func (s *ExecutionService) updateSubmissionResult(submission *model.CodeSubmission, output []byte, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
submission.Status = "failed"
|
||||
submission.Output = string(output) + "\n" + err.Error()
|
||||
} else {
|
||||
submission.Status = "completed"
|
||||
submission.Output = string(output)
|
||||
}
|
||||
}
|
||||
17
backend/service/uuid.go
Normal file
17
backend/service/uuid.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateUUID creates a random UUID
|
||||
func GenerateUUID() string {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
}
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
}
|
||||
Reference in New Issue
Block a user