working socket integration

This commit is contained in:
2025-04-21 21:03:59 +05:30
parent c143efa70e
commit 4453e69e68
22 changed files with 2070 additions and 60 deletions

View File

@@ -1,6 +1,8 @@
package service
import (
"bufio"
"context"
"fmt"
"io"
"log"
@@ -14,19 +16,24 @@ import (
"github.com/arnab-afk/monaco/model"
"github.com/arnab-afk/monaco/queue"
"github.com/gorilla/websocket"
)
// ExecutionService handles code execution for multiple languages
type ExecutionService struct {
mu sync.Mutex
queue *queue.JobQueue
mu sync.Mutex
queue *queue.JobQueue
wsConnections map[string]*websocket.Conn // Map of submission ID to WebSocket connection
wsInputChannels map[string]chan string // Map of submission ID to input channel
}
// NewExecutionService creates a new execution service
func NewExecutionService() *ExecutionService {
log.Println("Initializing execution service with 3 concurrent workers")
return &ExecutionService{
queue: queue.NewJobQueue(35), // 3 concurrent executions max
queue: queue.NewJobQueue(3), // 3 concurrent executions max
wsConnections: make(map[string]*websocket.Conn),
wsInputChannels: make(map[string]chan string),
}
}
@@ -483,3 +490,223 @@ func (s *ExecutionService) GetQueueStats() map[string]int {
stats["queue_length"], stats["running_jobs"], stats["max_workers"])
return stats
}
// HandleWebSocket handles a WebSocket connection for a code submission
func (s *ExecutionService) HandleWebSocket(conn *websocket.Conn, submission *model.CodeSubmission) {
// Store the WebSocket connection
s.mu.Lock()
s.wsConnections[submission.ID] = conn
// Create an input channel for this submission
inputChan := make(chan string, 10) // Buffer size of 10
s.wsInputChannels[submission.ID] = inputChan
s.mu.Unlock()
// Clean up when done
defer func() {
s.mu.Lock()
delete(s.wsConnections, submission.ID)
delete(s.wsInputChannels, submission.ID)
s.mu.Unlock()
conn.Close()
}()
// Start a goroutine to read input from the WebSocket
go func() {
for {
// Read message from WebSocket
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Printf("[WS-%s] Error reading message: %v", submission.ID, err)
break
}
// Only process text messages
if messageType == websocket.TextMessage {
// Send the input to the input channel
inputChan <- string(message)
}
}
}()
// Execute the code
submission.Status = "running"
submission.StartedAt = time.Now()
log.Printf("[WS-JOB-%s] Starting WebSocket execution for language: %s",
submission.ID, submission.Language)
// Execute the code based on the language
s.executeLanguageSpecificWithWebSocket(submission, inputChan, conn)
}
// executeLanguageSpecificWithWebSocket runs code in the appropriate language with WebSocket I/O
func (s *ExecutionService) executeLanguageSpecificWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
log.Printf("[WS-EXEC-%s] Selecting execution environment for language: %s",
submission.ID, submission.Language)
switch submission.Language {
case "python":
log.Printf("[WS-EXEC-%s] Executing Python code", submission.ID)
s.executePythonWithWebSocket(submission, inputChan, conn)
case "java":
log.Printf("[WS-EXEC-%s] Executing Java code", submission.ID)
s.executeJavaWithWebSocket(submission, inputChan, conn)
case "c":
log.Printf("[WS-EXEC-%s] Executing C code", submission.ID)
s.executeCWithWebSocket(submission, inputChan, conn)
case "cpp":
log.Printf("[WS-EXEC-%s] Executing C++ code", submission.ID)
s.executeCppWithWebSocket(submission, inputChan, conn)
default:
log.Printf("[WS-EXEC-%s] ERROR: Unsupported language: %s", submission.ID, submission.Language)
submission.Status = "failed"
output := "Unsupported language: " + submission.Language
submission.Output = output
// Send error message to WebSocket
conn.WriteMessage(websocket.TextMessage, []byte(output))
}
// Update submission status
submission.CompletedAt = time.Now()
submission.Status = "completed"
}
// executePythonWithWebSocket runs Python code with WebSocket for I/O
func (s *ExecutionService) executePythonWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
log.Printf("[WS-PYTHON-%s] Preparing Python WebSocket execution", submission.ID)
startTime := time.Now()
// Send initial message to client
conn.WriteMessage(websocket.TextMessage, []byte("Starting Python execution...\n"))
// Create a command to run Python in a Docker container
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)
// Get stdin pipe
stdin, err := cmd.StdinPipe()
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to create stdin pipe: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte("Error: Failed to create stdin pipe\n"))
return
}
// Get stdout and stderr pipes
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to create stdout pipe: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte("Error: Failed to create stdout pipe\n"))
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to create stderr pipe: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte("Error: Failed to create stderr pipe\n"))
return
}
// Start the command
if err := cmd.Start(); err != nil {
log.Printf("[WS-PYTHON-%s] Failed to start command: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Error: Failed to start command: %v\n", err)))
return
}
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Create a channel to signal when the command is done
done := make(chan struct{})
// Start a goroutine to handle command completion
go func() {
err := cmd.Wait()
if err != nil {
log.Printf("[WS-PYTHON-%s] Command failed: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\nExecution failed: %v\n", err)))
} else {
log.Printf("[WS-PYTHON-%s] Command completed successfully", submission.ID)
conn.WriteMessage(websocket.TextMessage, []byte("\nExecution completed successfully\n"))
}
close(done)
}()
// Start a goroutine to read from stdout and stderr
go func() {
scanner := bufio.NewScanner(io.MultiReader(stdout, stderr))
for scanner.Scan() {
line := scanner.Text()
log.Printf("[WS-PYTHON-%s] Output: %s", submission.ID, line)
conn.WriteMessage(websocket.TextMessage, []byte(line+"\n"))
}
}()
// Handle input from the WebSocket
go func() {
for {
select {
case input := <-inputChan:
log.Printf("[WS-PYTHON-%s] Received input: %s", submission.ID, input)
// Write the input to stdin
_, err := io.WriteString(stdin, input+"\n")
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to write to stdin: %v", submission.ID, err)
}
case <-ctx.Done():
return
case <-done:
return
}
}
}()
// Wait for the command to complete or timeout
select {
case <-ctx.Done():
log.Printf("[WS-PYTHON-%s] Execution timed out after 30 seconds", submission.ID)
conn.WriteMessage(websocket.TextMessage, []byte("\nExecution timed out after 30 seconds\n"))
cmd.Process.Kill()
case <-done:
// Command completed
}
elapsed := time.Since(startTime)
log.Printf("[WS-PYTHON-%s] Python execution completed in %v", submission.ID, elapsed)
// Update submission result
submission.CompletedAt = time.Now()
submission.Status = "completed"
}
// executeJavaWithWebSocket runs Java code with WebSocket for I/O
func (s *ExecutionService) executeJavaWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
// For now, just send a message that this is not implemented
conn.WriteMessage(websocket.TextMessage, []byte("Java WebSocket execution not yet implemented\n"))
submission.Status = "failed"
submission.Output = "Java WebSocket execution not yet implemented"
}
// executeCWithWebSocket runs C code with WebSocket for I/O
func (s *ExecutionService) executeCWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
// For now, just send a message that this is not implemented
conn.WriteMessage(websocket.TextMessage, []byte("C WebSocket execution not yet implemented\n"))
submission.Status = "failed"
submission.Output = "C WebSocket execution not yet implemented"
}
// executeCppWithWebSocket runs C++ code with WebSocket for I/O
func (s *ExecutionService) executeCppWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
// For now, just send a message that this is not implemented
conn.WriteMessage(websocket.TextMessage, []byte("C++ WebSocket execution not yet implemented\n"))
submission.Status = "failed"
submission.Output = "C++ WebSocket execution not yet implemented"
}