botserver/packages/basic.gblib/services/vm2-process/index.ts
2022-11-11 21:35:05 -03:00

137 lines
3.3 KiB
TypeScript

const crypto2 = require('crypto');
const Fs = require('fs');
const Path = require('path');
const { spawn } = require('child_process');
const CDP = require('chrome-remote-interface');
const { } = require('child_process');
const { dirname } = require('path');
const { fileURLToPath } = require('url');
const net = require('net');
import { GBLog } from 'botlib';
import { GBServer } from '../../../../src/app';
const genericPool = require('generic-pool');
const finalStream = require('final-stream');
const waitUntil = (condition) => {
if (condition()) {
return Promise.resolve();
}
return new Promise((resolve) => {
const interval = setInterval(() => {
if (!condition()) {
return;
}
clearInterval(interval);
resolve(0);
}, 0);
});
};
const createVm2Pool = ({ min, max, ...limits }) => {
limits = Object.assign({
cpu: 100,
memory: 2000,
time: 4000
}, limits);
let limitError = null;
const ref = crypto2.randomBytes(20).toString('hex');
const kill = (x) => {
spawn('sh', ['-c', `pkill -9 -f ${ref}`]);
};
let stderrCache = '';
const factory = {
create: function () {
const runner = spawn('cpulimit', [
'-ql', limits.cpu,
'--',
'node', `--inspect-brk=${limits.debuggerPort}`, `--experimental-fetch`, `--max-old-space-size=${limits.memory}`,
limits.script
, ref
], { cwd: limits.cwd, shell: false });
runner.stdout.on('data', (data) => {
runner.socket = runner.socket || data.toString().trim();
});
runner.stderr.on('data', (data) => {
stderrCache = stderrCache + data.toString();
if (stderrCache.includes('failed: address already in use'))
{
limitError = stderrCache;
}
if (stderrCache.includes('FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory')) {
limitError = 'code execution exceeed allowed memory';
}
});
return runner;
},
destroy: function (childProcess) {
kill(childProcess);
}
};
const pool = genericPool.createPool(factory, { min, max });
const run = async (code, scope) => {
const childProcess = await pool.acquire();
await waitUntil(() => childProcess.socket);
const socket = net.createConnection(childProcess.socket);
CDP(async (client) => {
const { Debugger, Runtime } = client;
try {
GBServer.globals.debuggers[scope.botId] = client;
} catch (err) {
GBLog.error(err);
} finally {
client.close();
}
}).on('error', (err) => {
console.error(err);
});
const timer = setTimeout(() => {
limitError = 'code execution took too long and was killed';
kill(childProcess);
}, limits.time);
socket.write(JSON.stringify({ code, scope }) + '\n');
try {
let data = await finalStream(socket);
data = JSON.parse(data)
if (data.error) {
throw new Error(data.error);
}
return data.result;
} catch (error) {
throw new Error(limitError || error);
} finally {
clearTimeout(timer);
pool.destroy(childProcess);
}
};
return {
run,
drain: () => {
pool.drain().then(() => pool.clear());
}
};
};
exports.createVm2Pool = createVm2Pool;