sanket3280/code-execution
0
1const { exec } = require('child_process');2const fs = require('fs').promises;3const path = require('path');4const crypto = require('crypto');5const executionLogger = require('../../utils/executionLogger');6 7class DockerExecutor {8 constructor() {9 this.tempDir = path.join(__dirname, '../../temp');10 }11 12 async executeCode({ sourceCode, language, input, timeLimit = 3, memoryLimit = 256 }) {13 const executionId = crypto.randomBytes(8).toString('hex');14 const workDir = path.join(this.tempDir, executionId);15 const startTime = Date.now();16 17 console.log(`\n๐ณ Docker Execution Started`);18 console.log(` ID: ${executionId}`);19 console.log(` Language: ${language}`);20 console.log(` Time Limit: ${timeLimit}s`);21 console.log(` Memory Limit: ${memoryLimit}MB`);22 23 try {24 // Create temp directory25 await fs.mkdir(workDir, { recursive: true });26 27 // Get language config28 const config = this.getLanguageConfig(language);29 console.log(` Docker Image: ${config.dockerImage}`);30 31 // Write files in parallel32 await Promise.all([33 fs.writeFile(path.join(workDir, config.fileName), sourceCode),34 fs.writeFile(path.join(workDir, 'input.txt'), input || '')35 ]);36 37 // Build Docker command38 const dockerCmd = this.buildDockerCommand({ workDir, config, timeLimit, memoryLimit, executionId });39 40 // Execute with minimal timeout41 const totalTimeout = config.compileCmd ? (timeLimit + 2) * 1000 : timeLimit * 1000;42 const result = await this.runDockerCommand(dockerCmd, totalTimeout);43 44 // Fast status determination45 let status = 'AC';46 const stderr = result.stderr;47 const stdout = result.stdout;48 49 if (result.exitCode === 124 || stderr.includes('Time Limit Exceeded')) {50 status = 'TLE';51 } else if (result.exitCode !== 0) {52 if ((stderr.includes('.java:') || stdout.includes('.java:')) && (stderr.includes('error:') || stdout.includes('error:'))) {53 status = 'CE';54 } else if ((stderr.includes('.cpp:') || stderr.includes('.c:')) && stderr.includes('error:')) {55 status = 'CE';56 } else if (stderr.includes('Exception') || stderr.includes('Error')) {57 status = 'RE';58 } else {59 status = 'RE';60 }61 }62 63 const totalTime = Date.now() - startTime;64 const statusIcon = status === 'AC' ? 'โ
' : status === 'TLE' ? 'โฑ๏ธ' : status === 'CE' ? '๐จ' : 'โ';65 66 console.log(`${statusIcon} Docker Execution Complete`);67 console.log(` Status: ${status}`);68 console.log(` Exit Code: ${result.exitCode}`);69 console.log(` Execution Time: ${result.executionTime}ms`);70 console.log(` Total Time: ${totalTime}ms`);71 if (result.stderr) {72 console.log(` Error: ${result.stderr.substring(0, 100)}${result.stderr.length > 100 ? '...' : ''}`);73 }74 75 const executionResult = {76 success: result.exitCode === 0,77 output: result.stdout.trim(),78 error: result.stderr.trim(),79 executionTime: result.executionTime,80 memoryUsed: 0,81 status82 };83 84 // Log to file85 await executionLogger.logDockerExecution({86 executionId,87 language,88 dockerImage: config.dockerImage,89 timeLimit,90 memoryLimit,91 result: executionResult92 });93 94 return executionResult;95 } catch (error) {96 const totalTime = Date.now() - startTime;97 console.log(`โ Docker Execution Failed`);98 console.log(` Error: ${error.message}`);99 console.log(` Total Time: ${totalTime}ms`);100 101 return { 102 success: false, 103 output: '', 104 error: error.message, 105 executionTime: 0,106 memoryUsed: 0,107 status: 'ERROR' 108 };109 } finally {110 try { 111 await fs.rm(workDir, { recursive: true, force: true }); 112 } catch (e) {}113 }114 }115 116 buildDockerCommand({ workDir, config, timeLimit, memoryLimit, executionId }) {117 const containerName = `codebattle_${executionId}`;118 119 // Convert Windows path to Docker format120 const dockerPath = workDir.replace(/\\/g, '/').replace(/^([A-Z]):/, (match, drive) => `/${drive.toLowerCase()}`);121 122 // Minimal security for speed123 let cmd = `docker run --rm --name ${containerName} `;124 cmd += `--memory=${memoryLimit}m --cpus=2.0 `;125 cmd += `--network=none `;126 cmd += `-v "${dockerPath}:/workspace" -w /workspace `;127 cmd += `${config.dockerImage} `;128 129 // Fast execution130 if (config.compileCmd) {131 cmd += `sh -c "${config.compileCmd} && ${config.runCmd}"`;132 } else {133 cmd += `sh -c "${config.runCmd}"`;134 }135 136 return cmd;137 }138 139 runDockerCommand(cmd, timeout) {140 return new Promise((resolve) => {141 const startTime = Date.now();142 let killed = false;143 144 const childProcess = exec(cmd, { 145 timeout, 146 maxBuffer: 5 * 1024 * 1024,147 shell: true,148 killSignal: 'SIGKILL'149 }, (error, stdout, stderr) => {150 const executionTime = Date.now() - startTime;151 152 if (error) {153 if (error.killed || error.signal === 'SIGTERM' || error.signal === 'SIGKILL' || killed) {154 resolve({155 exitCode: 124,156 stdout: stdout || '',157 stderr: 'Time Limit Exceeded',158 executionTime159 });160 } else {161 resolve({162 exitCode: error.code || 1,163 stdout: stdout || '',164 stderr: stderr || error.message,165 executionTime166 });167 }168 } else {169 resolve({ 170 exitCode: 0, 171 stdout: stdout || '', 172 stderr: stderr || '', 173 executionTime 174 });175 }176 });177 178 const killTimer = setTimeout(() => { 179 if (childProcess && !childProcess.killed) {180 killed = true;181 childProcess.kill('SIGKILL');182 const containerName = cmd.match(/--name (\S+)/)?.[1];183 if (containerName) {184 exec(`docker kill ${containerName}`, () => {});185 }186 }187 }, timeout + 1000);188 189 childProcess.on('exit', () => {190 clearTimeout(killTimer);191 });192 });193 }194 195 getLanguageConfig(language) {196 const configs = {197 'JAVASCRIPT_NODE': {198 dockerImage: 'node:18-alpine',199 fileName: 'solution.js',200 runCmd: 'node solution.js',201 compileCmd: null202 },203 'PYTHON3': {204 dockerImage: 'python:3.11-alpine',205 fileName: 'solution.py',206 runCmd: 'python solution.py',207 compileCmd: null208 },209 'JAVA17': {210 dockerImage: 'eclipse-temurin:17-alpine',211 fileName: 'Main.java',212 compileCmd: 'javac -J-Xms64m -J-Xmx256m Main.java 2>&1',213 runCmd: 'timeout 3 java -Xms64m -Xmx256m Main < input.txt 2>&1'214 },215 'CPP14': {216 dockerImage: 'gcc:12-alpine',217 fileName: 'solution.cpp',218 compileCmd: 'g++ -std=c++14 -O2 solution.cpp -o solution 2>&1',219 runCmd: 'timeout 3 ./solution < input.txt 2>&1'220 },221 'C': {222 dockerImage: 'gcc:12-alpine',223 fileName: 'solution.c',224 compileCmd: 'gcc -O2 solution.c -o solution 2>&1',225 runCmd: 'timeout 3 ./solution < input.txt 2>&1'226 }227 };228 229 return configs[language] || configs.JAVASCRIPT_NODE;230 }231 232 async healthCheck() {233 try {234 const result = await this.runDockerCommand('docker --version', 5000);235 return result.exitCode === 0;236 } catch (error) {237 return false;238 }239 }240}241 242module.exports = new DockerExecutor();243 