From bd1623c09c267619293f83f265fd370242583710 Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Sat, 17 Feb 2024 15:06:49 -0500 Subject: [PATCH 1/2] [sandbox] Catch broken pipes --- sandbox/src/index.ts | 8 ++- sandbox/src/run/cpp.ts | 1 - sandbox/src/run/csharp.ts | 126 ++++++++++++++++++++------------------ sandbox/src/run/java.ts | 122 ++++++++++++++++++------------------ 4 files changed, 135 insertions(+), 122 deletions(-) diff --git a/sandbox/src/index.ts b/sandbox/src/index.ts index b28f25f..ac16deb 100644 --- a/sandbox/src/index.ts +++ b/sandbox/src/index.ts @@ -11,7 +11,13 @@ import { runCpp } from './run/cpp.js'; export const timeoutSeconds = 30; -const RunResultKind = z.enum(['CompileFailed', 'TimeLimitExceeded', 'Completed', 'SandboxError', 'RunError']); +const RunResultKind = z.enum([ + 'CompileFailed', + 'TimeLimitExceeded', + 'Completed', + 'SandboxError', + 'RunError' +]); export type RunResultKind = z.infer; const RunResult = z diff --git a/sandbox/src/run/cpp.ts b/sandbox/src/run/cpp.ts index 6849837..a4756fd 100644 --- a/sandbox/src/run/cpp.ts +++ b/sandbox/src/run/cpp.ts @@ -4,7 +4,6 @@ import util from 'util'; import { RunResult, timeoutSeconds } from '../index.js'; import { IRunner, IRunnerParams, IRunnerReturn } from './types.js'; import kill from 'tree-kill'; -import * as fs from 'fs'; const execPromise = util.promisify(exec); diff --git a/sandbox/src/run/csharp.ts b/sandbox/src/run/csharp.ts index 657e06c..1007307 100644 --- a/sandbox/src/run/csharp.ts +++ b/sandbox/src/run/csharp.ts @@ -14,76 +14,80 @@ export const runCSharp: IRunner = async function (params: { console.log(`- RUN: ${params.srcDir}`); const child = spawn('dotnet run', { shell: true, cwd: params.srcDir }); - let outputBuffer = ''; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - outputBuffer += data.toString(); - params.outputCallback?.(data.toString()); - }); - child.stderr.setEncoding('utf8'); - child.stderr.on('data', (data) => { - outputBuffer += data.toString(); - params.outputCallback?.(data.toString()); - }); + try { + let outputBuffer = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + outputBuffer += data.toString(); + params.outputCallback?.(data.toString()); + }); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + outputBuffer += data.toString(); + params.outputCallback?.(data.toString()); + }); - let runStartTime = performance.now(); - child.stdin.write(params.input); - child.stdin.end(); + let runStartTime = performance.now(); + child.stdin.write(params.input); + child.stdin.end(); - let timeLimitExceeded = false; - let completedNormally = false; + let timeLimitExceeded = false; + let completedNormally = false; - return { - success: true, - runResult: new Promise((resolve) => { - child.on('close', () => { - completedNormally = !timeLimitExceeded; + return { + success: true, + runResult: new Promise((resolve) => { + child.on('close', () => { + completedNormally = !timeLimitExceeded; - let runEndTime = performance.now(); - const runtimeMilliseconds = Math.floor(runEndTime - runStartTime); + let runEndTime = performance.now(); + const runtimeMilliseconds = Math.floor(runEndTime - runStartTime); - if (completedNormally) { - clearTimeout(timeoutHandle); - resolve({ - kind: 'Completed', - output: outputBuffer, - exitCode: child.exitCode!, - runtimeMilliseconds - }); - } else { - console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`); - resolve({ - kind: 'TimeLimitExceeded', - output: outputBuffer, - resultKindReason: `Timeout after ${timeoutSeconds} seconds` - }); - } - }); + if (completedNormally) { + clearTimeout(timeoutHandle); + resolve({ + kind: 'Completed', + output: outputBuffer, + exitCode: child.exitCode!, + runtimeMilliseconds + }); + } else { + console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`); + resolve({ + kind: 'TimeLimitExceeded', + output: outputBuffer, + resultKindReason: `Timeout after ${timeoutSeconds} seconds` + }); + } + }); - let timeoutHandle = setTimeout(() => { - if (completedNormally) { - return; - } + let timeoutHandle = setTimeout(() => { + if (completedNormally) { + return; + } - console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`); - timeLimitExceeded = true; + console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`); + timeLimitExceeded = true; - child.stdin.end(); - child.stdin.destroy(); - child.stdout.destroy(); - child.stderr.destroy(); + child.stdin.end(); + child.stdin.destroy(); + child.stdout.destroy(); + child.stderr.destroy(); + if (child.pid !== undefined) { + kill(child.pid); + } + }, timeoutSeconds * 1000); + }), + killFunc() { if (child.pid !== undefined) { - kill(child.pid); - } - }, timeoutSeconds * 1000); - }), - killFunc() { - if (child.pid !== undefined) { - if (!completedNormally && !timeLimitExceeded) { - kill(child.pid); - params.outputCallback?.('\n[Manually stopped]'); + if (!completedNormally && !timeLimitExceeded) { + kill(child.pid); + params.outputCallback?.('\n[Manually stopped]'); + } } } - } - }; + }; + } catch (error) { + return { success: false, runResult: { kind: 'RunError' } }; + } }; diff --git a/sandbox/src/run/java.ts b/sandbox/src/run/java.ts index 17553f9..f11ad98 100644 --- a/sandbox/src/run/java.ts +++ b/sandbox/src/run/java.ts @@ -35,73 +35,77 @@ export const runJava: IRunner = async function ( console.log(`- RUN: ${params.mainClass}`); const runCommand = `java -cp "${join(params.srcDir, 'build')}" ${params.mainClass}`; - let outputBuffer = ''; - const child = spawn(runCommand, { shell: true }); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - outputBuffer += data.toString(); - }); - child.stderr.setEncoding('utf8'); - child.stderr.on('data', (data) => { - outputBuffer += data.toString(); - }); + try { + let outputBuffer = ''; + const child = spawn(runCommand, { shell: true }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + outputBuffer += data.toString(); + }); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + outputBuffer += data.toString(); + }); - let runStartTime = performance.now(); - child.stdin.write(params.input); - child.stdin.end(); + let runStartTime = performance.now(); + child.stdin.write(params.input); + child.stdin.end(); - let timeLimitExceeded = false; - let completedNormally = false; + let timeLimitExceeded = false; + let completedNormally = false; - return { - success: true, - runResult: new Promise(async (resolve) => { - child.on('close', () => { - completedNormally = !timeLimitExceeded; + return { + success: true, + runResult: new Promise(async (resolve) => { + child.on('close', () => { + completedNormally = !timeLimitExceeded; - let runEndTime = performance.now(); - const runtimeMilliseconds = Math.floor(runEndTime - runStartTime); + let runEndTime = performance.now(); + const runtimeMilliseconds = Math.floor(runEndTime - runStartTime); - if (completedNormally) { - clearTimeout(timeoutHandle); - resolve({ - kind: 'Completed', - output: outputBuffer, - exitCode: child.exitCode!, - runtimeMilliseconds - }); - } else { - console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`); - resolve({ - kind: 'TimeLimitExceeded', - output: outputBuffer, - resultKindReason: `Timeout after ${timeoutSeconds} seconds` - }); - } - }); + if (completedNormally) { + clearTimeout(timeoutHandle); + resolve({ + kind: 'Completed', + output: outputBuffer, + exitCode: child.exitCode!, + runtimeMilliseconds + }); + } else { + console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`); + resolve({ + kind: 'TimeLimitExceeded', + output: outputBuffer, + resultKindReason: `Timeout after ${timeoutSeconds} seconds` + }); + } + }); - let timeoutHandle = setTimeout(() => { - if (completedNormally) { - return; - } + let timeoutHandle = setTimeout(() => { + if (completedNormally) { + return; + } - console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`); - timeLimitExceeded = true; + console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`); + timeLimitExceeded = true; - child.stdin.end(); - child.stdin.destroy(); - child.stdout.destroy(); - child.stderr.destroy(); - child.kill('SIGKILL'); - }, timeoutSeconds * 1000); - }), - killFunc() { - if (child.pid !== undefined) { - if (!completedNormally && !timeLimitExceeded) { - kill(child.pid); - params.outputCallback?.('\n[Manually stopped]'); + child.stdin.end(); + child.stdin.destroy(); + child.stdout.destroy(); + child.stderr.destroy(); + child.kill('SIGKILL'); + }, timeoutSeconds * 1000); + }), + killFunc() { + if (child.pid !== undefined) { + if (!completedNormally && !timeLimitExceeded) { + kill(child.pid); + params.outputCallback?.('\n[Manually stopped]'); + } } } - } - }; + }; + } catch (error) { + return { success: false, runResult: { kind: 'RunError' } }; + } }; From 722ff8c199b015be33ea39a76be98d82868a8fc1 Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Sat, 17 Feb 2024 15:12:54 -0500 Subject: [PATCH 2/2] [sandbox] Create separate build dir for cpp --- sandbox/src/index.ts | 12 +++++------- sandbox/src/run/cpp.ts | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/sandbox/src/index.ts b/sandbox/src/index.ts index ac16deb..33f8af1 100644 --- a/sandbox/src/index.ts +++ b/sandbox/src/index.ts @@ -1,7 +1,7 @@ import dotenv from 'dotenv'; import fs from 'fs-extra'; import urlJoin from 'url-join'; -import { string, z } from 'zod'; +import { z } from 'zod'; import os, { EOL } from 'os'; import { join } from 'path'; import { simpleGit, SimpleGit } from 'simple-git'; @@ -90,12 +90,10 @@ async function cloneAndRun(submissionData: SubmissionGetData) { return; } const tmpDir = os.tmpdir(); - const buildDir = join(tmpDir, 'bwcontest-build'); - if (fs.existsSync(buildDir)) { - fs.removeSync(buildDir); + const repoDir = join(tmpDir, 'bwcontest-src'); + if (fs.existsSync(repoDir)) { + fs.removeSync(repoDir); } - fs.mkdirSync(buildDir); - const repoDir = join(buildDir, 'src'); fs.mkdirSync(repoDir); const teamRepoUrl = urlJoin( @@ -114,7 +112,7 @@ async function cloneAndRun(submissionData: SubmissionGetData) { try { if (submissionData.submission.teamLanguage === 'Java') { let res = await runJava({ - srcDir: buildDir, + srcDir: repoDir, mainFile: join(repoDir, problemName, problemName + '.java'), mainClass: problemName, input: submissionData.submission.problem.realInput diff --git a/sandbox/src/run/cpp.ts b/sandbox/src/run/cpp.ts index a4756fd..2db9403 100644 --- a/sandbox/src/run/cpp.ts +++ b/sandbox/src/run/cpp.ts @@ -4,6 +4,8 @@ import util from 'util'; import { RunResult, timeoutSeconds } from '../index.js'; import { IRunner, IRunnerParams, IRunnerReturn } from './types.js'; import kill from 'tree-kill'; +import os from 'os'; +import fs from 'fs-extra'; const execPromise = util.promisify(exec); @@ -20,9 +22,16 @@ interface IRunnerParamsCpp extends IRunnerParams { export const runCpp: IRunner = async function ( params: IRunnerParamsCpp ): IRunnerReturn { + const tmpDir = os.tmpdir(); + const buildDir = join(tmpDir, 'bwcontest-cpp'); + if (fs.existsSync(buildDir)) { + fs.removeSync(buildDir); + } + fs.mkdirSync(buildDir); + console.log(`- BUILD: ${params.problemName}`); - const configureCommand = `cmake -S ${params.srcDir} -B ${join(params.srcDir, 'build')}`; + const configureCommand = `cmake -S ${params.srcDir} -B ${buildDir}`; try { await execPromise(configureCommand); } catch (e) { @@ -34,7 +43,7 @@ export const runCpp: IRunner = async function ( }; } - const compileCommand = `cmake --build ${join(params.srcDir, 'build')} --target ${params.problemName}`; + const compileCommand = `cmake --build ${buildDir} --target ${params.problemName}`; try { await execPromise(compileCommand); } catch (e) { @@ -50,9 +59,9 @@ export const runCpp: IRunner = async function ( let runCommand = ''; if (params.cppPlatform === 'VisualStudio') { - runCommand = `${join(params.srcDir, 'build', 'Debug', `${params.problemName}.exe`)}`; + runCommand = `${join(buildDir, 'Debug', `${params.problemName}.exe`)}`; } else { - runCommand = `${join(params.srcDir, 'build', params.problemName)}`; + runCommand = `${join(buildDir, params.problemName)}`; } try { let outputBuffer = '';