diff --git a/extension/bwcontest/package-lock.json b/extension/bwcontest/package-lock.json index b7b5dc5..84a6676 100644 --- a/extension/bwcontest/package-lock.json +++ b/extension/bwcontest/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "axios": "^1.4.0", "fs-extra": "^11.1.1", + "tree-kill": "^1.2.2", "vsce": "^2.15.0" }, "devDependencies": { @@ -5300,7 +5301,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, "bin": { "tree-kill": "cli.js" } diff --git a/extension/bwcontest/package.json b/extension/bwcontest/package.json index cfa80d1..edd7d23 100644 --- a/extension/bwcontest/package.json +++ b/extension/bwcontest/package.json @@ -103,6 +103,7 @@ "dependencies": { "axios": "^1.4.0", "fs-extra": "^11.1.1", + "tree-kill": "^1.2.2", "vsce": "^2.15.0" } } diff --git a/extension/bwcontest/src/problemPanel.ts b/extension/bwcontest/src/problemPanel.ts index 7e09e6c..abac8ea 100644 --- a/extension/bwcontest/src/problemPanel.ts +++ b/extension/bwcontest/src/problemPanel.ts @@ -17,6 +17,8 @@ export class BWPanel { private readonly _extensionUri: vscode.Uri; private _disposables: vscode.Disposable[] = []; private static _context?: vscode.ExtensionContext; + private static _running: boolean; + private static _kill: Function | null; public static createOrShow(context: vscode.ExtensionContext) { this._context = context; @@ -85,7 +87,14 @@ export class BWPanel { this._panel.webview.html = this._getHtmlForWebview(webview); webview.onDidReceiveMessage(async (data) => { switch (data.type) { + case 'onKill': { + if (!BWPanel._running || !BWPanel._kill) { + break; + } + BWPanel._kill(); + } case 'onSubmit': { + await vscode.workspace.saveAll(); if (!data.value) { return; } @@ -104,20 +113,25 @@ export class BWPanel { data.value.teamId, data.value.problemId ); - } catch (reason) { - vscode.window.showErrorMessage('Unable to submit'); - console.error(reason); + } catch (err: any) { + vscode.window.showErrorMessage(err.message ?? 'Submission unsuccessful'); break; } vscode.window.showInformationMessage('Submitted!'); break; } case 'onRun': { + if (BWPanel._running === true) { + vscode.window.showErrorMessage('Already running'); + break; + } + await vscode.workspace.saveAll(); if (!data.value) { - return; + break; } const repoDir = extensionSettings().repoClonePath; - runJava( + BWPanel._running = true; + const process = await runJava( join( repoDir, 'BWContest', @@ -135,9 +149,29 @@ export class BWPanel { ), data.value.problemPascalName, data.value.input - ).then((output) => { - this._panel.webview.postMessage({ type: 'onOutput', value: output }); - }); + ); + if (!process) { + this._panel.webview.postMessage({ + type: 'onOutput', + value: '[An error occurred while running]' + }); + break; + } + process.output + .then((output) => { + this._panel.webview.postMessage({ type: 'onOutput', value: output }); + BWPanel._running = false; + BWPanel._kill = null; + }) + .catch(() => { + this._panel.webview.postMessage({ + type: 'onOutput', + value: '[An error occurred while running]' + }); + BWPanel._running = false; + BWPanel._kill = null; + }); + BWPanel._kill = process.kill; break; } case 'onStartup': { diff --git a/extension/bwcontest/src/run/java.ts b/extension/bwcontest/src/run/java.ts index b03fc57..13580f6 100644 --- a/extension/bwcontest/src/run/java.ts +++ b/extension/bwcontest/src/run/java.ts @@ -5,6 +5,7 @@ import { exec, spawn } from 'child_process'; import { extensionSettings } from '../extension'; import { error } from 'console'; import util = require('node:util'); +import kill = require('tree-kill'); const execPromise = util.promisify(exec); @@ -13,7 +14,7 @@ export async function runJava( mainFile: string, mainClass: string, input: string -): Promise { +): Promise<{ output: Promise; kill: Function | null }> { const javaPath = extensionSettings().javaPath; if (javaPath == '') { throw error('Java path not set'); @@ -30,22 +31,44 @@ export async function runJava( const runCommand = `${join(javaPath, 'java')} -cp "${buildDir}" ${mainClass}`; - return new Promise((resolve) => { - 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(); - }); - child.stdin.write(input); - child.stdin.end(); + const child = spawn(runCommand, { shell: true }); + let outputBuffer = ''; + return { + output: new Promise((resolve) => { + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + outputBuffer += data.toString(); + }); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + outputBuffer += data.toString(); + }); + child.stdin.write(input); + child.stdin.end(); - child.on('close', () => { - resolve(outputBuffer); - }); - }); + let resolved = false; + + child.on('close', () => { + if (!resolved) { + resolved = true; + resolve(outputBuffer); + } + }); + + setTimeout(() => { + if (!resolved) { + console.log('30 seconds reached, killing process'); + resolved = true; + child.kill('SIGKILL'); + resolve(outputBuffer + '\n[Timeout after 30 seconds]'); + } + }, 30000); + }), + kill: () => { + if (child.pid) { + outputBuffer += '\n[Manually stopped]'; + kill(child.pid); + } + } + }; } diff --git a/extension/bwcontest/src/submit.ts b/extension/bwcontest/src/submit.ts index 614df6b..c386657 100644 --- a/extension/bwcontest/src/submit.ts +++ b/extension/bwcontest/src/submit.ts @@ -58,6 +58,6 @@ export async function submitProblem( throw Error('Failed to post submission'); } if (!res.data.success) { - throw Error('Submission post unsuccessful'); + throw Error(res.data.message ?? 'Unknown error'); } } diff --git a/extension/bwcontest/webviews/components/ProblemPanel.svelte b/extension/bwcontest/webviews/components/ProblemPanel.svelte index 04b14c0..5cdb901 100644 --- a/extension/bwcontest/webviews/components/ProblemPanel.svelte +++ b/extension/bwcontest/webviews/components/ProblemPanel.svelte @@ -61,6 +61,10 @@ } } + function onKill() { + postMessage({type: 'onKill'}); + } + async function fetchProblemData() { if (sessionToken) { const res = await fetch(`http://localhost:5173/api/contest/${sessionToken}`); @@ -125,7 +129,11 @@ {/if}