Add concurrent setting

This commit is contained in:
1computer1 2019-05-17 01:55:24 -04:00
parent 990143864e
commit e8b9303b4c
4 changed files with 66 additions and 19 deletions

View file

@ -119,6 +119,7 @@ The container is locked down, so there is no networking, limited memory and CPU
- `memory` Max memory usage of a container. - `memory` Max memory usage of a container.
- `cpu` Max CPU usage of a container. - `cpu` Max CPU usage of a container.
- `timeout` Time limit for code in milliseconds. - `timeout` Time limit for code in milliseconds.
- `prepare` Whether to run containers on setup. - `prepare` Whether to start containers on setup.
Setting to true will speed up the first eval, but that language might not be used. Setting to true will speed up the first eval, but that language might not be used.
- `concurrent` Number of code evaluations per language than can run at a time.
0. Run `node .` 0. Run `node .`

View file

@ -11,5 +11,6 @@
"memory": "256m", "memory": "256m",
"cpus": "0.25", "cpus": "0.25",
"timeout": 10000, "timeout": 10000,
"prepare": false "prepare": false,
"concurrent": 10
} }

View file

@ -1,6 +1,7 @@
const { AkairoHandler } = require('discord-akairo'); const { AkairoHandler } = require('discord-akairo');
const { Collection } = require('discord.js'); const { Collection } = require('discord.js');
const Language = require('./Language'); const Language = require('./Language');
const Queue = require('./Queue');
const childProcess = require('child_process'); const childProcess = require('child_process');
const util = require('util'); const util = require('util');
const path = require('path'); const path = require('path');
@ -24,6 +25,7 @@ class LanguageHandler extends AkairoHandler {
this.aliases = new Collection(); this.aliases = new Collection();
this.containers = new Collection(); this.containers = new Collection();
this.queues = new Collection();
} }
register(language, filepath) { register(language, filepath) {
@ -56,6 +58,7 @@ class LanguageHandler extends AkairoHandler {
return Promise.all(loads.map(async name => { return Promise.all(loads.map(async name => {
const folder = path.join(__dirname, '../../docker', name); const folder = path.join(__dirname, '../../docker', name);
await util.promisify(childProcess.exec)(`docker build -t "1computer1/comp_iler:${name}" ${folder}`); await util.promisify(childProcess.exec)(`docker build -t "1computer1/comp_iler:${name}" ${folder}`);
this.queues.set(id, new Queue(10));
if (this.client.config.prepare) { if (this.client.config.prepare) {
await this.setupContainer(id); await this.setupContainer(id);
} }
@ -89,25 +92,29 @@ class LanguageHandler extends AkairoHandler {
this.containers.get(id).count += 1; this.containers.get(id).count += 1;
} }
async evalCode({ language, code, options }) { evalCode({ language, code, options }) {
const { id = language.id, env = {} } = language.runWith(options); const { id = language.id, env = {} } = language.runWith(options);
const { name, count } = await this.setupContainer(id); const queue = this.queues.get(id);
this.incrementCount(id); return queue.enqueue(async () => {
const proc = childProcess.spawn('docker', [ const { name, count } = await this.setupContainer(id);
'exec', this.incrementCount(id);
`-eCOUNT=${count}`,
...Object.entries(env).map(([k, v]) => `-e${k}=${v}`),
name, '/bin/sh', '/var/run/run.sh', code
]);
try { const proc = childProcess.spawn('docker', [
const result = await this.handleSpawn(proc); 'exec',
return result; `-eCOUNT=${count}`,
} catch (err) { ...Object.entries(env).map(([k, v]) => `-e${k}=${v}`),
this.containers.delete(id); name, '/bin/sh', '/var/run/run.sh', code
await this.kill(name); ]);
throw err;
} try {
const result = await this.handleSpawn(proc);
return result;
} catch (err) {
this.containers.delete(id);
await this.kill(name);
throw err;
}
});
} }
handleSpawn(proc) { handleSpawn(proc) {

38
src/struct/Queue.js Normal file
View file

@ -0,0 +1,38 @@
class Queue {
constructor(limit) {
this.limit = limit;
this.tasks = [];
this.ongoing = 0;
}
get length() {
return this.tasks.length;
}
enqueue(task) {
return new Promise((resolve, reject) => {
this.tasks.push({ task, resolve, reject });
if (this.ongoing <= this.limit) {
this.process();
}
});
}
async process() {
this.ongoing++;
const { task, resolve, reject } = this.tasks.shift();
try {
const x = await task();
resolve(x);
} catch (e) {
reject(e);
}
this.ongoing--;
while (this.ongoing <= this.limit && this.tasks.length > 0) {
this.process();
}
}
}
module.exports = Queue;