Add concurrent setting
This commit is contained in:
parent
990143864e
commit
e8b9303b4c
4 changed files with 66 additions and 19 deletions
|
@ -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 .`
|
||||||
|
|
|
@ -11,5 +11,6 @@
|
||||||
"memory": "256m",
|
"memory": "256m",
|
||||||
"cpus": "0.25",
|
"cpus": "0.25",
|
||||||
"timeout": 10000,
|
"timeout": 10000,
|
||||||
"prepare": false
|
"prepare": false,
|
||||||
|
"concurrent": 10
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
38
src/struct/Queue.js
Normal 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;
|
Loading…
Reference in a new issue