CoolFace
Apppublic

Ratan1870/code-execution

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
docker.executor.js256 linesDownload Raw Back to services
1// ─── Docker Executor Service ────────────────────────2// Core engine: creates Docker sandbox, runs code, collects metrics3// BATCH MODE: compile once, run all test cases in ONE container4 5const { execFile } = require("child_process");6const { exec: execCb } = require("child_process");7const { promisify } = require("util");8const path = require("path");9const config = require("../config");10const {11  createWorkDir,12  writeFile,13  readFile,14  cleanup,15  ensureDir,16} = require("../utils/file.util");17const logger = require("../utils/logger.util");18 19const execFileAsync = promisify(execFile);20const execAsync = promisify(execCb);21 22class DockerExecutor {23  /**24   * Execute code with a SINGLE input (used by /api/execute)25   * Wraps batchExecute with 1 test case26   */27  async execute({ language, code, input = "", timeLimit, memoryLimit }) {28    const results = await this.batchExecute({29      language,30      code,31      inputs: [input],32      timeLimit,33      memoryLimit,34    });35    return results[0];36  }37 38  /**39   * Execute code against MULTIPLE inputs in ONE Docker container.40   * This is the core method — compile once, run N times.41   *42   * Flow:43   *  1. Create temp dir with code + all input files44   *  2. ONE Docker container compiles + runs all test cases45   *  3. Read all results from mounted volume46   *  4. Cleanup47   *48   * @param {Object} params49   * @param {string} params.language - c | cpp | python50   * @param {string} params.code - Source code51   * @param {string[]} params.inputs - Array of stdin inputs52   * @param {number} params.timeLimit - Time limit per test case (seconds)53   * @param {number} params.memoryLimit - Memory limit (MB)54   * @returns {Object[]} Array of execution results55   */56  async batchExecute({ language, code, inputs = [], timeLimit, memoryLimit }) {57    const langConfig = config.LANGUAGES[language];58    if (!langConfig) {59      throw new Error(`Unsupported language: ${language}`);60    }61 62    timeLimit = Math.min(63      timeLimit || config.DEFAULT_TIME_LIMIT,64      config.MAX_TIME_LIMIT,65    );66    memoryLimit = Math.min(67      memoryLimit || config.DEFAULT_MEMORY_LIMIT,68      config.MAX_MEMORY_LIMIT,69    );70 71    const numCases = inputs.length;72    const workDir = await createWorkDir();73    const codeDir = path.join(workDir, "code");74    const tcDir = path.join(workDir, "testcases");75    const resDir = path.join(workDir, "results");76 77    try {78      // Create directories79      await Promise.all([ensureDir(tcDir), ensureDir(resDir)]);80 81      // Write code file + all input files in parallel82      const writeOps = [83        writeFile(path.join(codeDir, langConfig.fileName), code),84      ];85      for (let i = 0; i < numCases; i++) {86        writeOps.push(87          writeFile(path.join(tcDir, `${i + 1}.in`), inputs[i] || ""),88        );89      }90      await Promise.all(writeOps);91 92      // Make everything writable by container93      await execAsync(`chmod -R 777 ${workDir}`);94 95      // ─── Run ONE Docker container for ALL test cases ───96      const startTime = process.hrtime.bigint();97      const dockerResult = await this._runDocker(98        workDir,99        language,100        timeLimit,101        memoryLimit,102        numCases,103      );104      const wallTimeNs = process.hrtime.bigint() - startTime;105      const wallTimeMs = Number(wallTimeNs / 1_000_000n);106 107      logger.info(108        `🐳 Container finished: ${numCases} test cases in ${wallTimeMs}ms`,109      );110 111      // ─── Read ALL results in parallel ───112      const readOps = [];113      for (let i = 0; i < numCases; i++) {114        const idx = i + 1;115        readOps.push(116          Promise.all([117            readFile(path.join(resDir, `${idx}.out`)),118            readFile(path.join(resDir, `${idx}.err`)),119            readFile(path.join(resDir, `${idx}.meta`)),120          ]),121        );122      }123      const allResults = await Promise.all(readOps);124 125      // ─── Parse each result ───126      const results = allResults.map(([output, stderr, metaRaw], i) => {127        const meta = this._parseMeta(metaRaw);128 129        if (!meta.verdict) {130          if (dockerResult.oomKilled) meta.verdict = "MLE";131          else if (dockerResult.exitCode === 137) meta.verdict = "MLE";132          else if (dockerResult.exitCode !== 0) meta.verdict = "RE";133          else meta.verdict = "IE";134        }135 136        return {137          verdict: meta.verdict,138          output: this._truncate(output, 10000),139          error: this._truncate(stderr, 5000),140          executionTime: meta.time || 0,141          memoryUsed: meta.memory || 0,142          exitCode: meta.exitCode ?? dockerResult.exitCode,143          wallTime: wallTimeMs,144          language,145          timeLimit,146          memoryLimit,147        };148      });149 150      return results;151    } catch (err) {152      logger.error(`Execution failed: ${err.message}`);153      return inputs.map(() => ({154        verdict: "IE",155        output: "",156        error: `Internal Error: ${err.message}`,157        executionTime: 0,158        memoryUsed: 0,159        exitCode: -1,160        wallTime: 0,161        language,162        timeLimit,163        memoryLimit,164      }));165    } finally {166      await cleanup(workDir);167    }168  }169 170  /**171   * Launch Docker container — batch mode172   * run.sh <language> <time_limit> <num_test_cases>173   */174  async _runDocker(workDir, language, timeLimit, memoryLimit, numCases) {175    // Total timeout = time_limit * numCases + 20s overhead (compile + Docker)176    const totalTimeout = timeLimit * numCases + 20;177 178    const args = [179      "run",180      "--rm",181      "--network=none",182      `--memory=${memoryLimit}m`,183      `--memory-swap=${memoryLimit}m`,184      "--cpus=1",185      "--pids-limit=64",186      "--cap-drop=ALL",187      "--security-opt=no-new-privileges",188      "--ulimit",189      "fsize=10485760:10485760",190      "--ulimit",191      "nofile=64:64",192      "-v",193      `${workDir}:/sandbox`,194      config.SANDBOX_IMAGE,195      language,196      String(timeLimit),197      String(numCases),198    ];199 200    try {201      const { stdout, stderr } = await execFileAsync("docker", args, {202        timeout: totalTimeout * 1000,203        maxBuffer: 10 * 1024 * 1024,204      });205      return { exitCode: 0, stdout, stderr, oomKilled: false };206    } catch (err) {207      const exitCode = err.code || 1;208      const oomKilled = exitCode === 137;209 210      if (err.killed) {211        logger.warn("⚠️  Docker container killed by Node.js timeout");212        return {213          exitCode: 124,214          stdout: "",215          stderr: "Timed out",216          oomKilled: false,217        };218      }219 220      return {221        exitCode,222        stdout: err.stdout || "",223        stderr: err.stderr || "",224        oomKilled,225      };226    }227  }228 229  /**230   * Parse key=value metadata from run.sh231   */232  _parseMeta(raw) {233    const meta = {};234    if (!raw || !raw.trim()) return meta;235    for (const line of raw.trim().split("\n")) {236      const eq = line.indexOf("=");237      if (eq === -1) continue;238      const key = line.substring(0, eq).trim();239      const val = line.substring(eq + 1).trim();240      if (key === "verdict") meta.verdict = val;241      else if (key === "time") meta.time = parseInt(val) || 0;242      else if (key === "memory") meta.memory = parseInt(val) || 0;243      else if (key === "exitCode") meta.exitCode = parseInt(val) || 0;244    }245    return meta;246  }247 248  _truncate(str, maxLen) {249    if (!str) return "";250    if (str.length <= maxLen) return str;251    return str.substring(0, maxLen) + `\n... (truncated)`;252  }253}254 255module.exports = new DockerExecutor();256