From bd1623c09c267619293f83f265fd370242583710 Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Sat, 17 Feb 2024 15:06:49 -0500 Subject: [PATCH] [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' } }; + } };