From 56a3c91a6af100fe83e8907220e17c28abbd74b2 Mon Sep 17 00:00:00 2001 From: Brikaa Date: Sun, 26 Sep 2021 13:25:13 +0200 Subject: [PATCH 01/11] pkg(iverilog-11.0.0): Added iverilog 11.0.0 --- api/src/api/v2.js | 4 ++-- cli/package-lock.json | 4 ++-- packages/iverilog/11.0.0/build.sh | 17 +++++++++++++++++ packages/iverilog/11.0.0/compile | 4 ++++ packages/iverilog/11.0.0/environment | 2 ++ packages/iverilog/11.0.0/metadata.json | 5 +++++ packages/iverilog/11.0.0/run | 4 ++++ packages/iverilog/11.0.0/test.verilog | 7 +++++++ readme.md | 5 +++-- repo/Dockerfile | 2 +- 10 files changed, 47 insertions(+), 7 deletions(-) create mode 100755 packages/iverilog/11.0.0/build.sh create mode 100644 packages/iverilog/11.0.0/compile create mode 100644 packages/iverilog/11.0.0/environment create mode 100644 packages/iverilog/11.0.0/metadata.json create mode 100644 packages/iverilog/11.0.0/run create mode 100644 packages/iverilog/11.0.0/test.verilog diff --git a/api/src/api/v2.js b/api/src/api/v2.js index 215453b..e3e0522 100644 --- a/api/src/api/v2.js +++ b/api/src/api/v2.js @@ -146,7 +146,7 @@ router.ws('/connect', async (ws, req) => { eventBus.on("exit", (stage, status) => ws.send(JSON.stringify({type: "exit", stage, ...status}))) ws.on("message", async (data) => { - + try{ const msg = JSON.parse(data); @@ -194,7 +194,7 @@ router.ws('/connect', async (ws, req) => { } break; } - + }catch(error){ ws.send(JSON.stringify({type: "error", message: error.message})) ws.close(4002, "Notified Error") diff --git a/cli/package-lock.json b/cli/package-lock.json index f7c2771..335ed21 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "piston-cli", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "piston-cli", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "axios": "^0.21.2", diff --git a/packages/iverilog/11.0.0/build.sh b/packages/iverilog/11.0.0/build.sh new file mode 100755 index 0000000..befb2fa --- /dev/null +++ b/packages/iverilog/11.0.0/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +PREFIX=$(realpath $(dirname $0)) + +mkdir -p build/iverilog +cd build/iverilog +curl -L https://github.com/steveicarus/iverilog/archive/refs/tags/v11_0.tar.gz -o iverilog.tar.gz +tar xzf iverilog.tar.gz --strip-components=1 + +chmod +x ./autoconf.sh +./autoconf.sh +./configure --prefix="$PREFIX" +make -j$(nproc) +make install -j$(nproc) + +cd ../../ +rm -rf build diff --git a/packages/iverilog/11.0.0/compile b/packages/iverilog/11.0.0/compile new file mode 100644 index 0000000..56f4b4e --- /dev/null +++ b/packages/iverilog/11.0.0/compile @@ -0,0 +1,4 @@ +#!/bin/bash + +rename 's/$/\.v/' "$@" # Add .v extension +iverilog *.v diff --git a/packages/iverilog/11.0.0/environment b/packages/iverilog/11.0.0/environment new file mode 100644 index 0000000..b482830 --- /dev/null +++ b/packages/iverilog/11.0.0/environment @@ -0,0 +1,2 @@ +#!/bin/bash +export PATH=$PWD/bin:$PATH diff --git a/packages/iverilog/11.0.0/metadata.json b/packages/iverilog/11.0.0/metadata.json new file mode 100644 index 0000000..5a35bde --- /dev/null +++ b/packages/iverilog/11.0.0/metadata.json @@ -0,0 +1,5 @@ +{ + "language": "iverilog", + "version": "11.0.0", + "aliases": ["verilog", "vvp"] +} diff --git a/packages/iverilog/11.0.0/run b/packages/iverilog/11.0.0/run new file mode 100644 index 0000000..39e898c --- /dev/null +++ b/packages/iverilog/11.0.0/run @@ -0,0 +1,4 @@ +#!/bin/bash + +shift +vvp a.out "$@" diff --git a/packages/iverilog/11.0.0/test.verilog b/packages/iverilog/11.0.0/test.verilog new file mode 100644 index 0000000..88fcd7a --- /dev/null +++ b/packages/iverilog/11.0.0/test.verilog @@ -0,0 +1,7 @@ +module hello; + initial + begin + $display("OK"); + $finish ; + end +endmodule diff --git a/readme.md b/readme.md index a154fe6..2737bd9 100644 --- a/readme.md +++ b/readme.md @@ -42,10 +42,10 @@
# Notes About Hacktoberfest - + While we are accepting pull requests for Hacktoberfest, we will reject any low-quality PRs. If we see PR abuse for Hacktoberfest, we will stop providing Hacktoberfest approval for pull requests. - + We are accepting PRs for: * Packages - updating package versions, adding new packages * Documentation updates @@ -343,6 +343,7 @@ Content-Type: application/json `golfscript`, `groovy`, `haskell`, +`iverilog`, `java`, `javascript`, `jelly`, diff --git a/repo/Dockerfile b/repo/Dockerfile index 56ca59d..de28c11 100644 --- a/repo/Dockerfile +++ b/repo/Dockerfile @@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y unzip autoconf build-essential libssl-d libncursesw5-dev python3-pip libgmp-dev libmpfr-dev python2 libffi-dev gfortran\ libreadline-dev libblas-dev liblapack-dev libpcre3-dev libarpack2-dev libfftw3-dev \ libglpk-dev libqhull-dev libqrupdate-dev libsuitesparse-dev libsundials-dev \ - libbz2-dev liblzma-dev libpcre2-dev && \ + libbz2-dev liblzma-dev libpcre2-dev gperf bison flex g++ && \ ln -sf /bin/bash /bin/sh && \ rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3.7 2 From 474c986879886180a6ab72eb1497588610a29fa5 Mon Sep 17 00:00:00 2001 From: Brikaa Date: Sun, 26 Sep 2021 14:02:03 +0200 Subject: [PATCH 02/11] Add semantic versioning in CONTRIBUTING.MD --- packages/CONTRIBUTING.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/CONTRIBUTING.MD b/packages/CONTRIBUTING.MD index 813f71e..b1ed6d3 100644 --- a/packages/CONTRIBUTING.MD +++ b/packages/CONTRIBUTING.MD @@ -2,7 +2,7 @@ ## Naming Languages -Languages should be named after their interpreters, and the command line binaries you call. +Languages should be named after their interpreters, and the command line binaries you call. The language version should use semantic versioning. For example, the full name of the standard python interpreter is `CPython`, however we would name it `python`, after the main binary which it provides. In the example of NodeJS, we would call this `node`, after the main binary. From e5ac7a2acc3196f811fa1fe65828c5a4076f9667 Mon Sep 17 00:00:00 2001 From: Brikaa Date: Sun, 26 Sep 2021 14:09:25 +0200 Subject: [PATCH 03/11] Add ./piston logs --- piston | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/piston b/piston index 28e18db..a14e7f5 100755 --- a/piston +++ b/piston @@ -19,6 +19,7 @@ case $1 in echo "Commands:" echo " select Select the environment" echo " docker_compose Interact directly with the docker-compose for the selected environment" + echo " logs Show docker-compose logs" echo echo " start Starts piston" echo " stop Stops piston" @@ -37,18 +38,19 @@ case $1 in echo " clean-repo Remove all packages from local repo" echo " build-pkg Build a package" echo " rebuild Build and restart the docker container" - + else echo " Switch to developement environment for more info" echo " > piston select dev" - + fi ;; select) echo "$2" > .piston_env ;; docker_compose) shift; docker_compose "$@";; + logs) docker_compose logs -f ;; restart) docker_compose restart ;; start) docker_compose up -d ;; From 1835ab5cab354e79f05e052b4c58c3ecc2ba9617 Mon Sep 17 00:00:00 2001 From: Thomas Hobson Date: Thu, 30 Sep 2021 08:11:47 +1300 Subject: [PATCH 04/11] Add self to license --- license | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/license b/license index 4f45aea..fd203f8 100644 --- a/license +++ b/license @@ -1,4 +1,4 @@ -Copyright (c) 2018-2021 Brian Seymour, EMKC Contributors +Copyright (c) 2018-2021 Brian Seymour, Thomas Hobson, EMKC Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 7313958155b3a0e136b5a5df0946d3e78dfca081 Mon Sep 17 00:00:00 2001 From: Thomas Hobson Date: Fri, 1 Oct 2021 20:28:54 +1300 Subject: [PATCH 05/11] api: maximum concurrent jobs and potential fix for gcc --- api/src/config.js | 7 +++++++ api/src/job.js | 38 +++++++++++++++++++++++++++++++------- docs/configuration.md | 9 +++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/api/src/config.js b/api/src/config.js index 84270aa..bbd7ae9 100644 --- a/api/src/config.js +++ b/api/src/config.js @@ -114,6 +114,13 @@ const options = [ 'https://github.com/engineer-man/piston/releases/download/pkgs/index', validators: [], }, + { + key: 'max_concurrent_jobs', + desc: 'Maximum number of concurrent jobs to run at one time', + default: 64, + parser: parse_int, + validators: [(x) => x > 0 || `${x} cannot be negative`] + } ]; logger.info(`Loading Configuration from environment`); diff --git a/api/src/job.js b/api/src/job.js index 683cda6..712dcd8 100644 --- a/api/src/job.js +++ b/api/src/job.js @@ -16,6 +16,19 @@ const job_states = { let uid = 0; let gid = 0; +let remainingJobSpaces = config.max_concurrent_jobs; +let jobQueue = []; + + +setInterval(()=>{ + // Every 10ms try resolve a new job, if there is an available slot + if(jobQueue.length > 0 && remainingJobSpaces > 0){ + jobQueue.shift()() + } +}, 10) + + + class Job { constructor({ runtime, files, args, stdin, timeouts, memory_limits }) { this.uuid = uuidv4(); @@ -48,8 +61,15 @@ class Job { } async prime() { - logger.info(`Priming job uuid=${this.uuid}`); + if(remainingJobSpaces < 1){ + logger.info(`Awaiting job slot uuid=${this.uuid}`) + await new Promise((resolve)=>{ + jobQueue.push(resolve) + }) + } + logger.info(`Priming job uuid=${this.uuid}`); + remainingJobSpaces--; logger.debug('Writing files to job cache'); logger.debug(`Transfering ownership uid=${this.uid} gid=${this.gid}`); @@ -152,21 +172,23 @@ class Job { } }); - const exit_cleanup = () => { + const exit_cleanup = async () => { clear_timeout(kill_timeout); proc.stderr.destroy(); proc.stdout.destroy(); + + await this.cleanup_processes() }; - proc.on('exit', (code, signal) => { - exit_cleanup(); + proc.on('exit', async (code, signal) => { + await exit_cleanup(); resolve({stdout, stderr, code, signal, output }); }); - proc.on('error', err => { - exit_cleanup(); + proc.on('error', async err => { + await exit_cleanup(); reject({ error: err, stdout, stderr, output }); }); @@ -339,11 +361,13 @@ class Job { async cleanup() { logger.info(`Cleaning up job uuid=${this.uuid}`); - await this.cleanup_processes(); await this.cleanup_filesystem(); + + remainingJobSpaces++; } } + module.exports = { Job, }; diff --git a/docs/configuration.md b/docs/configuration.md index 1388e9d..16a5df0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -145,3 +145,12 @@ default: https://github.com/engineer-man/piston/releases/download/pkgs/index ``` URL for repository index, where packages will be downloaded from. + +## Maximum Concurrent Jobs + +```yaml +key: PISTON_MAX_CONCURRENT_JOBS +default: 64 +``` + +Maximum number of jobs to run concurrently. From 1b6563d1817679ff487c6007c3745b9d463fd449 Mon Sep 17 00:00:00 2001 From: Thomas Hobson Date: Sat, 2 Oct 2021 00:07:37 +1300 Subject: [PATCH 06/11] rework process janitor Old process janitor required starting a `ps` process. This was problematic, as `ps` requires another entry in the process table, which in some cases was impossible as it was exhausted. --- api/src/job.js | 74 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/api/src/job.js b/api/src/job.js index 712dcd8..ecc4ab3 100644 --- a/api/src/job.js +++ b/api/src/job.js @@ -146,26 +146,31 @@ class Job { const kill_timeout = set_timeout( - _ => proc.kill('SIGKILL'), + async _ => { + logger.info(`Timeout exceeded timeout=${timeout} uuid=${this.uuid}`) + process.kill(proc.pid, 'SIGKILL') + }, timeout ); - proc.stderr.on('data', data => { + proc.stderr.on('data', async data => { if(eventBus !== null) { eventBus.emit("stderr", data); } else if (stderr.length > config.output_max_size) { - proc.kill('SIGKILL'); + logger.info(`stderr length exceeded uuid=${this.uuid}`) + process.kill(proc.pid, 'SIGKILL') } else { stderr += data; output += data; } }); - proc.stdout.on('data', data => { + proc.stdout.on('data', async data => { if(eventBus !== null){ eventBus.emit("stdout", data); } else if (stdout.length > config.output_max_size) { - proc.kill('SIGKILL'); + logger.info(`stdout length exceeded uuid=${this.uuid}`) + process.kill(proc.pid, 'SIGKILL') } else { stdout += data; output += data; @@ -179,6 +184,7 @@ class Job { proc.stdout.destroy(); await this.cleanup_processes() + logger.debug(`Finished exit cleanup uuid=${this.uuid}`) }; proc.on('exit', async (code, signal) => { @@ -284,36 +290,47 @@ class Job { this.state = job_states.EXECUTED; } - async cleanup_processes() { + async cleanup_processes(dont_wait = []) { let processes = [1]; + logger.debug(`Cleaning up processes uuid=${this.uuid}`) while (processes.length > 0) { - processes = await new Promise((resolve, reject) => - cp.execFile('ps', ['awwxo', 'pid,ruid'], (err, stdout) => { - if (err === null) { - const lines = stdout.split('\n').slice(1); //Remove header with slice - const procs = lines.map(line => { - const [pid, ruid] = line - .trim() - .split(/\s+/) - .map(n => parseInt(n)); + processes = [] - return { pid, ruid }; - }); - resolve(procs); - } else { - reject(error); - } - }) - ); + const proc_ids = await fs.readdir("/proc"); + + + processes = await Promise.all(proc_ids.map(async (proc_id) => { + if(isNaN(proc_id)) return -1; + try{ + const proc_status = await fs.read_file(path.join("/proc",proc_id,"status")); + const proc_lines = proc_status.to_string().split("\n") + const uid_line = proc_lines.find(line=>line.starts_with("Uid:")) + const [_, ruid, euid, suid, fuid] = uid_line.split(/\s+/); + + + if(ruid == this.uid || euid == this.uid) + return parse_int(proc_id) + + }catch{ + return -1 + } + + return -1 + })) + + processes = processes.filter(p => p > 0) + + if(processes.length > 0) + logger.debug(`Got processes to kill: ${processes} uuid=${this.uuid}`) + - processes = processes.filter(proc => proc.ruid === this.uid); for (const proc of processes) { // First stop the processes, but keep their resources allocated so they cant re-fork try { - process.kill(proc.pid, 'SIGSTOP'); + process.kill(proc, 'SIGSTOP'); } catch { // Could already be dead } @@ -322,14 +339,17 @@ class Job { for (const proc of processes) { // Then clear them out of the process tree try { - process.kill(proc.pid, 'SIGKILL'); + process.kill(proc, 'SIGKILL'); } catch { // Could already be dead and just needs to be waited on } - wait_pid(proc.pid); + if(!dont_wait.includes(proc)) + wait_pid(proc); } } + + logger.debug(`Cleaned up processes uuid=${this.uuid}`) } async cleanup_filesystem() { From 6d50745db896daf39cd35f1942fa06abf07b65bd Mon Sep 17 00:00:00 2001 From: Dan Vargas Date: Fri, 6 Aug 2021 13:30:48 -0600 Subject: [PATCH 07/11] pkg(forte-1.0.0): add forte --- packages/forte/1.0.0/build.sh | 15 +++++++++++++++ packages/forte/1.0.0/environment | 4 ++++ packages/forte/1.0.0/metadata.json | 5 +++++ packages/forte/1.0.0/run | 3 +++ packages/forte/1.0.0/test.forte | 2 ++ readme.md | 1 + 6 files changed, 30 insertions(+) create mode 100755 packages/forte/1.0.0/build.sh create mode 100644 packages/forte/1.0.0/environment create mode 100644 packages/forte/1.0.0/metadata.json create mode 100644 packages/forte/1.0.0/run create mode 100644 packages/forte/1.0.0/test.forte diff --git a/packages/forte/1.0.0/build.sh b/packages/forte/1.0.0/build.sh new file mode 100755 index 0000000..b3e39a8 --- /dev/null +++ b/packages/forte/1.0.0/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# the forter interpreter requries ruby +source ../../ruby/3.0.1/build.sh + +mkdir -p build + +git clone -q "https://github.com/judofyr/forter" build/forter +cd build/forter + +mv bin/* ../../bin/ +mv lib/* ../../lib/ + +cd ../../ +rm -rf build diff --git a/packages/forte/1.0.0/environment b/packages/forte/1.0.0/environment new file mode 100644 index 0000000..e75919d --- /dev/null +++ b/packages/forte/1.0.0/environment @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# interpreter location +export PATH=$PWD/bin:$PATH diff --git a/packages/forte/1.0.0/metadata.json b/packages/forte/1.0.0/metadata.json new file mode 100644 index 0000000..fd4ec12 --- /dev/null +++ b/packages/forte/1.0.0/metadata.json @@ -0,0 +1,5 @@ +{ + "language": "forte", + "version": "1.0.0", + "aliases": ["forter"] +} diff --git a/packages/forte/1.0.0/run b/packages/forte/1.0.0/run new file mode 100644 index 0000000..79ee95b --- /dev/null +++ b/packages/forte/1.0.0/run @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +forter "$@" diff --git a/packages/forte/1.0.0/test.forte b/packages/forte/1.0.0/test.forte new file mode 100644 index 0000000..7482949 --- /dev/null +++ b/packages/forte/1.0.0/test.forte @@ -0,0 +1,2 @@ +1 PRINT "OK" +2 END diff --git a/readme.md b/readme.md index 01f3a2a..e95de75 100644 --- a/readme.md +++ b/readme.md @@ -306,6 +306,7 @@ Content-Type: application/json `elixir`, `emacs`, `erlang`, +`forte`, `fortran`, `go`, `golfscript`, From 528073932632e8dd46f7fba54dd5a1212cbfc190 Mon Sep 17 00:00:00 2001 From: Dan Vargas Date: Fri, 17 Sep 2021 09:59:24 -0500 Subject: [PATCH 08/11] pkg(freebasic-1.8.0): Add Freebasic --- packages/freebasic/1.8.0/build.sh | 5 +++++ packages/freebasic/1.8.0/compile | 4 ++++ packages/freebasic/1.8.0/environment | 4 ++++ packages/freebasic/1.8.0/metadata.json | 5 +++++ packages/freebasic/1.8.0/run | 5 +++++ packages/freebasic/1.8.0/test.bas | 1 + readme.md | 1 + 7 files changed, 25 insertions(+) create mode 100755 packages/freebasic/1.8.0/build.sh create mode 100644 packages/freebasic/1.8.0/compile create mode 100644 packages/freebasic/1.8.0/environment create mode 100644 packages/freebasic/1.8.0/metadata.json create mode 100644 packages/freebasic/1.8.0/run create mode 100644 packages/freebasic/1.8.0/test.bas diff --git a/packages/freebasic/1.8.0/build.sh b/packages/freebasic/1.8.0/build.sh new file mode 100755 index 0000000..bd58488 --- /dev/null +++ b/packages/freebasic/1.8.0/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +curl -L "https://sourceforge.net/projects/fbc/files/FreeBASIC-1.08.0/Binaries-Linux/FreeBASIC-1.08.0-linux-x86_64.tar.gz/download" -o freebasic.tar.gz +tar xf freebasic.tar.gz --strip-components=1 +rm freebasic.tar.gz diff --git a/packages/freebasic/1.8.0/compile b/packages/freebasic/1.8.0/compile new file mode 100644 index 0000000..b836b3d --- /dev/null +++ b/packages/freebasic/1.8.0/compile @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# Compile bas files +fbc -lang qb -b "$@" -x out diff --git a/packages/freebasic/1.8.0/environment b/packages/freebasic/1.8.0/environment new file mode 100644 index 0000000..144c737 --- /dev/null +++ b/packages/freebasic/1.8.0/environment @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# Path to fbc compiler +export PATH=$PWD/bin:$PATH diff --git a/packages/freebasic/1.8.0/metadata.json b/packages/freebasic/1.8.0/metadata.json new file mode 100644 index 0000000..1dcf1ff --- /dev/null +++ b/packages/freebasic/1.8.0/metadata.json @@ -0,0 +1,5 @@ +{ + "language": "freebasic", + "version": "1.8.0", + "aliases": ["bas", "fbc", "basic", "qbasic", "quickbasic"] +} diff --git a/packages/freebasic/1.8.0/run b/packages/freebasic/1.8.0/run new file mode 100644 index 0000000..610d7e6 --- /dev/null +++ b/packages/freebasic/1.8.0/run @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# Run output file from compile with arguments +shift +./out "$@" diff --git a/packages/freebasic/1.8.0/test.bas b/packages/freebasic/1.8.0/test.bas new file mode 100644 index 0000000..b13a0ef --- /dev/null +++ b/packages/freebasic/1.8.0/test.bas @@ -0,0 +1 @@ +PRINT "OK" \ No newline at end of file diff --git a/readme.md b/readme.md index c942624..53625ab 100644 --- a/readme.md +++ b/readme.md @@ -339,6 +339,7 @@ Content-Type: application/json `emacs`, `erlang`, `fortran`, +`freebasic`, `go`, `golfscript`, `groovy`, From 4870441574bced4f83777c9bc12235d5953ef83c Mon Sep 17 00:00:00 2001 From: Brikaa Date: Fri, 1 Oct 2021 21:41:09 +0200 Subject: [PATCH 09/11] config.js: timeout, overrides --- api/src/api/v2.js | 6 ++-- api/src/config.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/api/src/api/v2.js b/api/src/api/v2.js index e3e0522..e0abacd 100644 --- a/api/src/api/v2.js +++ b/api/src/api/v2.js @@ -109,8 +109,8 @@ function get_job(body){ stdin: stdin || "", files, timeouts: { - run: run_timeout || 3000, - compile: compile_timeout || 10000, + run: run_timeout || config.run_timeout, + compile: compile_timeout || config.compile_timeout, }, memory_limits: { run: run_memory_limit || config.run_memory_limit, @@ -228,7 +228,7 @@ router.post('/execute', async (req, res) => { return res.status(200).send(result); }catch(error){ - return res.status(400).json(error); + return res.status(400).json(error.to_string()); } }); diff --git a/api/src/config.js b/api/src/config.js index bbd7ae9..162c9d6 100644 --- a/api/src/config.js +++ b/api/src/config.js @@ -2,6 +2,46 @@ const fss = require('fs'); const Logger = require('logplease'); const logger = Logger.create('config'); +function parse_overrides(overrides) { + try { + return JSON.parse(overrides); + } + catch (e) { + return null; + } +} + +function validate_overrides(overrides, options) { + for (let language in overrides) { + for (let key in overrides[language]) { + if ( + ![ + 'max_process_count', 'max_open_files', 'max_file_size', + 'compile_memory_limit', 'run_memory_limit', 'compile_timeout', + 'run_timeout', 'output_max_size' + ].includes(key) + ) { + logger.error(`Invalid overridden option: ${key}`); + return false; + } + let option = options.find((o) => o.key == key); + let parser = option.parser; + let raw = overrides[language][key]; + let value = parser(raw); + let validators = option.validators; + for (let validator of validators) { + let response = validator(value, raw); + if (response !== true) { + logger.error(`Failed to validate overridden option: ${key}`, response); + return false; + } + } + overrides[language][key] = value; + } + } + return overrides; +} + const options = [ { key: 'log_level', @@ -91,6 +131,22 @@ const options = [ parser: parse_int, validators: [(x, raw) => !is_nan(x) || `${raw} is not a number`], }, + { + key: 'compile_timeout', + desc: + 'Max time allowed for compile stage in milliseconds', + default: 10000, // 10 seconds + parser: parse_int, + validators: [(x, raw) => !is_nan(x) || `${raw} is not a number`], + }, + { + key: 'run_timeout', + desc: + 'Max time allowed for run stage in milliseconds', + default: 3000, // 3 seconds + parser: parse_int, + validators: [(x, raw) => !is_nan(x) || `${raw} is not a number`], + }, { key: 'compile_memory_limit', desc: @@ -120,6 +176,16 @@ const options = [ default: 64, parser: parse_int, validators: [(x) => x > 0 || `${x} cannot be negative`] + }, + { + key: 'limit_overrides', + desc: 'Per-language exceptions in JSON format for each of:\ + max_process_count, max_open_files, max_file_size, compile_memory_limit,\ + run_memory_limit, compile_timeout, run_timeout, output_max_size', + default: {}, + parser: parse_overrides, + validators: [(x) => !!x || `Invalid JSON format for the overrides\n${x}`] + // More validation is done after the configs are loaded } ]; @@ -138,7 +204,7 @@ options.forEach(option => { const parsed_val = parser(env_val); - const value = env_val || option.default; + const value = parsed_val || option.default; option.validators.for_each(validator => { let response = null; @@ -158,10 +224,16 @@ options.forEach(option => { config[option.key] = value; }); +let overrides = validate_overrides(config.limit_overrides, options) +errored = errored || !overrides; + if (errored) { process.exit(1); } +config.limit_overrides = overrides; +console.log(config.limit_overrides); + logger.info('Configuration successfully loaded'); module.exports = config; From 1c675b5f8f4df011211ffbf949f9521faf39dc23 Mon Sep 17 00:00:00 2001 From: Hydrazer Date: Sat, 2 Oct 2021 16:50:37 -0600 Subject: [PATCH 10/11] pkg(japt-2.0.0): add japt --- packages/japt/2.0.0/build.sh | 6 ++++++ packages/japt/2.0.0/environment | 5 +++++ packages/japt/2.0.0/metadata.json | 5 +++++ packages/japt/2.0.0/run | 4 ++++ packages/japt/2.0.0/test.japt | 1 + readme.md | 1 + 6 files changed, 22 insertions(+) create mode 100644 packages/japt/2.0.0/build.sh create mode 100644 packages/japt/2.0.0/environment create mode 100644 packages/japt/2.0.0/metadata.json create mode 100644 packages/japt/2.0.0/run create mode 100644 packages/japt/2.0.0/test.japt diff --git a/packages/japt/2.0.0/build.sh b/packages/japt/2.0.0/build.sh new file mode 100644 index 0000000..d31a5cc --- /dev/null +++ b/packages/japt/2.0.0/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# japt install +source ../../node/16.3.0/build.sh + +git clone -q "https://github.com/Hydrazer/japt.git" japt \ No newline at end of file diff --git a/packages/japt/2.0.0/environment b/packages/japt/2.0.0/environment new file mode 100644 index 0000000..1fd5f14 --- /dev/null +++ b/packages/japt/2.0.0/environment @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# node and japt path +export PATH=$PWD/bin:$PATH +export JAPT_PATH=$PWD/japt \ No newline at end of file diff --git a/packages/japt/2.0.0/metadata.json b/packages/japt/2.0.0/metadata.json new file mode 100644 index 0000000..7a3e5aa --- /dev/null +++ b/packages/japt/2.0.0/metadata.json @@ -0,0 +1,5 @@ +{ + "language": "japt", + "version": "2.0.0", + "aliases": ["japt"] +} \ No newline at end of file diff --git a/packages/japt/2.0.0/run b/packages/japt/2.0.0/run new file mode 100644 index 0000000..80649c4 --- /dev/null +++ b/packages/japt/2.0.0/run @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# japt only takes filename and stdin +node "$JAPT_PATH"/node.js "$1" \ No newline at end of file diff --git a/packages/japt/2.0.0/test.japt b/packages/japt/2.0.0/test.japt new file mode 100644 index 0000000..d096585 --- /dev/null +++ b/packages/japt/2.0.0/test.japt @@ -0,0 +1 @@ +"OK \ No newline at end of file diff --git a/readme.md b/readme.md index 02a3a03..5b41e6a 100644 --- a/readme.md +++ b/readme.md @@ -346,6 +346,7 @@ Content-Type: application/json `groovy`, `haskell`, `iverilog`, +`japt`, `java`, `javascript`, `jelly`, From 94af5639bf5130c91aaa260cdf0fb151b04f73ce Mon Sep 17 00:00:00 2001 From: Brikaa Date: Sat, 2 Oct 2021 14:08:36 +0200 Subject: [PATCH 11/11] Add per-language constraint overrides --- api/src/api/v2.js | 93 ++++++++++++++------------------ api/src/config.js | 22 ++++---- api/src/job.js | 37 ++++++------- api/src/runtime.js | 48 ++++++++++++++++- packages/dotnet/5.0.201/build.sh | 0 5 files changed, 112 insertions(+), 88 deletions(-) mode change 100644 => 100755 packages/dotnet/5.0.201/build.sh diff --git a/api/src/api/v2.js b/api/src/api/v2.js index e0abacd..a3571e1 100644 --- a/api/src/api/v2.js +++ b/api/src/api/v2.js @@ -3,7 +3,6 @@ const router = express.Router(); const events = require('events'); -const config = require('../config'); const runtime = require('../runtime'); const { Job } = require('../job'); const package = require('../package'); @@ -13,7 +12,7 @@ const SIGNALS = ["SIGABRT","SIGALRM","SIGBUS","SIGCHLD","SIGCLD","SIGCONT","SIGE // ref: https://man7.org/linux/man-pages/man7/signal.7.html function get_job(body){ - const { + let { language, version, args, @@ -31,19 +30,16 @@ function get_job(body){ message: 'language is required as a string', }); } - if (!version || typeof version !== 'string') { return reject({ message: 'version is required as a string', }); } - if (!files || !Array.isArray(files)) { return reject({ message: 'files is required as an array', }); } - for (const [i, file] of files.entries()) { if (typeof file.content !== 'string') { return reject({ @@ -52,73 +48,64 @@ function get_job(body){ } } - if (compile_memory_limit) { - if (typeof compile_memory_limit !== 'number') { - return reject({ - message: 'if specified, compile_memory_limit must be a number', - }); - } - - if ( - config.compile_memory_limit >= 0 && - (compile_memory_limit > config.compile_memory_limit || - compile_memory_limit < 0) - ) { - return reject({ - message: - 'compile_memory_limit cannot exceed the configured limit of ' + - config.compile_memory_limit, - }); - } - } - - if (run_memory_limit) { - if (typeof run_memory_limit !== 'number') { - return reject({ - message: 'if specified, run_memory_limit must be a number', - }); - } - - if ( - config.run_memory_limit >= 0 && - (run_memory_limit > config.run_memory_limit || run_memory_limit < 0) - ) { - return reject({ - message: - 'run_memory_limit cannot exceed the configured limit of ' + - config.run_memory_limit, - }); - } - } - const rt = runtime.get_latest_runtime_matching_language_version( language, version ); - if (rt === undefined) { return reject({ message: `${language}-${version} runtime is unknown`, }); } + for (let constraint of ['memory_limit', 'timeout']) { + for (let type of ['compile', 'run']) { + let constraint_name = `${type}_${constraint}`; + let constraint_value = body[constraint_name]; + let configured_limit = rt[`${constraint}s`][type]; + if (!constraint_value) { + continue; + } + if (typeof constraint_value !== 'number') { + return reject({ + message: `If specified, ${constraint_name} must be a number` + }); + } + if (configured_limit <= 0) { + continue; + } + if (constraint_value > configured_limit) { + return reject({ + message: `${constraint_name} cannot exceed the configured limit of ${configured_limit}` + }); + } + if (constraint_value < 0) { + return reject({ + message: `${constraint_name} must be non-negative` + }); + } + } + } + + compile_timeout = compile_timeout || rt.timeouts.compile; + run_timeout = run_timeout || rt.timeouts.run; + compile_memory_limit = compile_memory_limit || rt.memory_limits.compile; + run_timeout = run_timeout || rt.timeouts.run; resolve(new Job({ runtime: rt, - alias: language, args: args || [], stdin: stdin || "", files, timeouts: { - run: run_timeout || config.run_timeout, - compile: compile_timeout || config.compile_timeout, + run: run_timeout, + compile: compile_timeout, }, memory_limits: { - run: run_memory_limit || config.run_memory_limit, - compile: compile_memory_limit || config.compile_memory_limit, + run: run_memory_limit, + compile: compile_memory_limit, } })); - }) - + }); } router.use((req, res, next) => { @@ -228,7 +215,7 @@ router.post('/execute', async (req, res) => { return res.status(200).send(result); }catch(error){ - return res.status(400).json(error.to_string()); + return res.status(400).json(error); } }); diff --git a/api/src/config.js b/api/src/config.js index 162c9d6..c191644 100644 --- a/api/src/config.js +++ b/api/src/config.js @@ -24,7 +24,7 @@ function validate_overrides(overrides, options) { logger.error(`Invalid overridden option: ${key}`); return false; } - let option = options.find((o) => o.key == key); + let option = options.find((o) => o.key === key); let parser = option.parser; let raw = overrides[language][key]; let value = parser(raw); @@ -38,8 +38,10 @@ function validate_overrides(overrides, options) { } overrides[language][key] = value; } + // Modifies the reference + options[options.index_of(options.find((o) => o.key === 'limit_overrides'))] = overrides; } - return overrides; + return true; } const options = [ @@ -184,8 +186,10 @@ const options = [ run_memory_limit, compile_timeout, run_timeout, output_max_size', default: {}, parser: parse_overrides, - validators: [(x) => !!x || `Invalid JSON format for the overrides\n${x}`] - // More validation is done after the configs are loaded + validators: [ + (x) => !!x || `Invalid JSON format for the overrides\n${x}`, + (overrides, _, options) => validate_overrides(overrides, options) || `Failed to validate the overrides` + ] } ]; @@ -208,8 +212,8 @@ options.forEach(option => { option.validators.for_each(validator => { let response = null; - if (env_val) response = validator(parsed_val, env_val); - else response = validator(value, value); + if (env_val) response = validator(parsed_val, env_val, options); + else response = validator(value, value, options); if (response !== true) { errored = true; @@ -224,16 +228,10 @@ options.forEach(option => { config[option.key] = value; }); -let overrides = validate_overrides(config.limit_overrides, options) -errored = errored || !overrides; - if (errored) { process.exit(1); } -config.limit_overrides = overrides; -console.log(config.limit_overrides); - logger.info('Configuration successfully loaded'); module.exports = config; diff --git a/api/src/job.js b/api/src/job.js index ecc4ab3..d4d19d9 100644 --- a/api/src/job.js +++ b/api/src/job.js @@ -30,7 +30,7 @@ setInterval(()=>{ class Job { - constructor({ runtime, files, args, stdin, timeouts, memory_limits }) { + constructor({ runtime, files, args, stdin }) { this.uuid = uuidv4(); this.runtime = runtime; this.files = files.map((file, i) => ({ @@ -40,8 +40,6 @@ class Job { this.args = args; this.stdin = stdin; - this.timeouts = timeouts; - this.memory_limits = memory_limits; this.uid = config.runner_uid_min + uid; this.gid = config.runner_gid_min + gid; @@ -102,9 +100,9 @@ class Job { const prlimit = [ 'prlimit', - '--nproc=' + config.max_process_count, - '--nofile=' + config.max_open_files, - '--fsize=' + config.max_file_size, + '--nproc=' + this.runtime.max_process_count , + '--nofile=' + this.runtime.max_open_files , + '--fsize=' + this.runtime.max_file_size , ]; if (memory_limit >= 0) { @@ -142,8 +140,6 @@ class Job { proc.kill(signal) }) } - - const kill_timeout = set_timeout( async _ => { @@ -156,7 +152,7 @@ class Job { proc.stderr.on('data', async data => { if(eventBus !== null) { eventBus.emit("stderr", data); - } else if (stderr.length > config.output_max_size) { + } else if (stderr.length > this.runtime.output_max_size) { logger.info(`stderr length exceeded uuid=${this.uuid}`) process.kill(proc.pid, 'SIGKILL') } else { @@ -168,7 +164,7 @@ class Job { proc.stdout.on('data', async data => { if(eventBus !== null){ eventBus.emit("stdout", data); - } else if (stdout.length > config.output_max_size) { + } else if (stdout.length > this.runtime.output_max_size) { logger.info(`stdout length exceeded uuid=${this.uuid}`) process.kill(proc.pid, 'SIGKILL') } else { @@ -223,8 +219,8 @@ class Job { compile = await this.safe_call( path.join(this.runtime.pkgdir, 'compile'), this.files.map(x => x.name), - this.timeouts.compile, - this.memory_limits.compile + this.runtime.timeouts.compile, + this.runtime.memory_limits.compile ); } @@ -233,8 +229,8 @@ class Job { const run = await this.safe_call( path.join(this.runtime.pkgdir, 'run'), [this.files[0].name, ...this.args], - this.timeouts.run, - this.memory_limits.run + this.runtime.timeouts.run, + this.runtime.memory_limits.run ); this.state = job_states.EXECUTED; @@ -266,8 +262,8 @@ class Job { const {error, code, signal} = await this.safe_call( path.join(this.runtime.pkgdir, 'compile'), this.files.map(x => x.name), - this.timeouts.compile, - this.memory_limits.compile, + this.runtime.timeouts.compile, + this.runtime.memory_limits.compile, eventBus ) @@ -279,14 +275,14 @@ class Job { const {error, code, signal} = await this.safe_call( path.join(this.runtime.pkgdir, 'run'), [this.files[0].name, ...this.args], - this.timeouts.run, - this.memory_limits.run, + this.runtime.timeouts.run, + this.runtime.memory_limits.run, eventBus ); eventBus.emit("exit", "run", {error, code, signal}) - + this.state = job_states.EXECUTED; } @@ -308,8 +304,7 @@ class Job { const proc_lines = proc_status.to_string().split("\n") const uid_line = proc_lines.find(line=>line.starts_with("Uid:")) const [_, ruid, euid, suid, fuid] = uid_line.split(/\s+/); - - + if(ruid == this.uid || euid == this.uid) return parse_int(proc_id) diff --git a/api/src/runtime.js b/api/src/runtime.js index 191fc5d..60d3c23 100644 --- a/api/src/runtime.js +++ b/api/src/runtime.js @@ -8,12 +8,54 @@ const path = require('path'); const runtimes = []; class Runtime { - constructor({ language, version, aliases, pkgdir, runtime }) { + constructor({ + language, version, aliases, pkgdir, runtime, timeouts, memory_limits, max_process_count, + max_open_files, max_file_size, output_max_size + }) { this.language = language; this.version = version; this.aliases = aliases || []; this.pkgdir = pkgdir; this.runtime = runtime; + this.timeouts = timeouts; + this.memory_limits = memory_limits; + this.max_process_count = max_process_count; + this.max_open_files = max_open_files; + this.max_file_size = max_file_size; + this.output_max_size = output_max_size; + } + + static compute_single_limit(language_name, limit_name, language_limit_overrides) { + return ( + config.limit_overrides[language_name] && config.limit_overrides[language_name][limit_name] + || language_limit_overrides && language_limit_overrides[limit_name] + || config[limit_name] + ); + } + + static compute_all_limits(language_name, language_limit_overrides) { + return { + timeouts: { + compile: + this.compute_single_limit(language_name, 'compile_timeout', language_limit_overrides), + run: + this.compute_single_limit(language_name, 'run_timeout', language_limit_overrides) + }, + memory_limits: { + compile: + this.compute_single_limit(language_name, 'compile_memory_limit', language_limit_overrides), + run: + this.compute_single_limit(language_name, 'run_memory_limit', language_limit_overrides) + }, + max_process_count: + this.compute_single_limit(language_name, 'max_process_count', language_limit_overrides), + max_open_files: + this.compute_single_limit(language_name, 'max_open_files', language_limit_overrides), + max_file_size: + this.compute_single_limit(language_name, 'max_file_size', language_limit_overrides), + output_max_size: + this.compute_single_limit(language_name, 'output_max_size', language_limit_overrides), + } } static load_package(package_dir) { @@ -21,7 +63,7 @@ class Runtime { fss.read_file_sync(path.join(package_dir, 'pkg-info.json')) ); - let { language, version, build_platform, aliases, provides } = info; + let { language, version, build_platform, aliases, provides, limit_overrides } = info; version = semver.parse(version); if (build_platform !== globals.platform) { @@ -41,6 +83,7 @@ class Runtime { version, pkgdir: package_dir, runtime: language, + ...Runtime.compute_all_limits(lang.language, lang.limit_overrides) }) ); }); @@ -51,6 +94,7 @@ class Runtime { version, aliases, pkgdir: package_dir, + ...Runtime.compute_all_limits(language, limit_overrides) }) ); } diff --git a/packages/dotnet/5.0.201/build.sh b/packages/dotnet/5.0.201/build.sh old mode 100644 new mode 100755