new(all): Alpha Word Debugger for 3.0.
This commit is contained in:
parent
79f77b528d
commit
301de1f73c
4 changed files with 145 additions and 127 deletions
|
@ -89,9 +89,7 @@ export class DebuggerService {
|
||||||
*/
|
*/
|
||||||
maxLines: number = 2000;
|
maxLines: number = 2000;
|
||||||
|
|
||||||
debugMap = {};
|
|
||||||
conversationsMap = {};
|
conversationsMap = {};
|
||||||
scopeMap = {};
|
|
||||||
watermarkMap = {};
|
watermarkMap = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,54 +106,47 @@ export class DebuggerService {
|
||||||
'Debug Web Automation',
|
'Debug Web Automation',
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const botId = min.botId;
|
||||||
|
|
||||||
|
GBServer.globals.debuggers[botId] = {};
|
||||||
|
GBServer.globals.debuggers[botId].state = 1;
|
||||||
|
GBServer.globals.debuggers[botId].breaks = [];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private client;
|
private client;
|
||||||
|
|
||||||
public async setBreakpoint({ botId, botApiKey, line }) {
|
public async breakpoint({ botId, botApiKey, line }) {
|
||||||
|
|
||||||
const client = GBServer.globals.debuggers[botId];
|
GBServer.globals.debuggers[botId].breaks.push(Number.parseInt(line));
|
||||||
|
|
||||||
async function mainScript({ Debugger }) {
|
|
||||||
return new Promise((fulfill, reject) => {
|
|
||||||
Debugger.scriptParsed((params) => {
|
|
||||||
const { scriptId, url } = params;
|
|
||||||
fulfill(scriptId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const scriptId = await mainScript(client);
|
|
||||||
const { breakpointId } = await await client.Debugger.setBreakpoint({
|
|
||||||
location: {
|
|
||||||
scriptId,
|
|
||||||
lineNumber: line - 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async removeBreakPoint({ botId, botApiKey, line }) {
|
public async removeBreakPoint({ botId, botApiKey, line }) {
|
||||||
|
const client = GBServer.globals.debuggers[botId].client;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async continueRun({ botId, botApiKey, force }) {
|
public async continueRun({ botId, botApiKey, force }) {
|
||||||
const client = GBServer.globals.debuggers[botId];
|
const client = GBServer.globals.debuggers[botId].client;
|
||||||
client.Debugger.resume();
|
client.Debugger.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop({ botId, botApiKey, force }) {
|
public async stop({ botId, botApiKey, force }) {
|
||||||
const client = GBServer.globals.debuggers[botId];
|
const client = GBServer.globals.debuggers[botId].client;
|
||||||
client.close();
|
client.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stepOver({ botId, botApiKey }) {
|
public async stepOver({ botId, botApiKey }) {
|
||||||
const client = GBServer.globals.debuggers[botId];
|
const client = GBServer.globals.debuggers[botId].client;
|
||||||
client.stepOver();
|
client.stepOver();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getExecutionContext({ botId, botApiKey, force }) {
|
public async getExecutionContext({ botId, botApiKey, force }) {
|
||||||
|
|
||||||
const client = GBServer.globals.debuggers[botId];
|
const client = GBServer.globals.debuggers[botId].client;
|
||||||
const conversationId = this.conversationsMap[botId];
|
const conversationId = this.conversationsMap[botId];
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,12 +166,17 @@ export class DebuggerService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { state:this.debugMap[botId].state, messages, scope: this.scopeMap[botId] };
|
|
||||||
|
return { state: GBServer.globals.debuggers[botId].state, messages, scope: GBServer.globals.debuggers[botId].scope };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async run({ botId, botApiKey, scriptName }) {
|
public async run({ botId, botApiKey, scriptName }) {
|
||||||
|
|
||||||
this.debugMap[botId] = { state: 1 };
|
GBLog.info(`BASIC: Running ${botId} in DEBUG mode.`);
|
||||||
|
|
||||||
|
|
||||||
|
GBServer.globals.debuggers[botId].state = 1;
|
||||||
|
|
||||||
|
|
||||||
let min: GBMinInstance = GBServer.globals.minInstances.filter(
|
let min: GBMinInstance = GBServer.globals.minInstances.filter(
|
||||||
p => p.instance.botId === botId
|
p => p.instance.botId === botId
|
||||||
|
@ -210,28 +206,5 @@ export class DebuggerService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup debugger.
|
|
||||||
|
|
||||||
const client = GBServer.globals.debuggers[botId];
|
|
||||||
|
|
||||||
client.Debugger.paused(({ callFrames, reason, hitBreakpoints }) => {
|
|
||||||
|
|
||||||
const frame = callFrames[0];
|
|
||||||
if (hitBreakpoints.length > 1) {
|
|
||||||
GBLog.info(`.gbdialog break at line ${frame.location.lineNumber + 1}`); // (zero-based)
|
|
||||||
|
|
||||||
const scope = `${frame.scopeChain[0].name} ${frame.scopeChain[0].object}`;
|
|
||||||
|
|
||||||
this.scopeMap[botId] = scope;
|
|
||||||
}
|
|
||||||
else if (reason === ''){
|
|
||||||
GBLog.info(`.gbdialog ${reason} at line ${frame.location.lineNumber + 1}`); // (zero-based)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.Runtime.runIfWaitingForDebugger();
|
|
||||||
await client.Debugger.enable();
|
|
||||||
await client.Debugger.setPauseOnExceptions('all');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -826,9 +826,9 @@ export class GBVMService extends GBService {
|
||||||
const runnerPath = urlJoin(process.cwd(), 'dist', 'packages', 'basic.gblib', 'services', 'vm2-process', 'vm2ProcessRunner.js');
|
const runnerPath = urlJoin(process.cwd(), 'dist', 'packages', 'basic.gblib', 'services', 'vm2-process', 'vm2ProcessRunner.js');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { run, drain } = createVm2Pool({
|
const { run } = createVm2Pool({
|
||||||
min: 1,
|
min: 0,
|
||||||
max: 1,
|
max: 0,
|
||||||
debuggerPort: 9222,
|
debuggerPort: 9222,
|
||||||
botId: botId,
|
botId: botId,
|
||||||
cpu: 100,
|
cpu: 100,
|
||||||
|
@ -841,7 +841,6 @@ export class GBVMService extends GBService {
|
||||||
const port = run.port;
|
const port = run.port;
|
||||||
const result = await run(code, { filename: scriptPath, sandbox: sandbox });
|
const result = await run(code, { filename: scriptPath, sandbox: sandbox });
|
||||||
|
|
||||||
drain();
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`BASIC RUNTIME ERR: ${error.message ? error.message : error}\n Stack:${error.stack}`);
|
throw new Error(`BASIC RUNTIME ERR: ${error.message ? error.message : error}\n Stack:${error.stack}`);
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
const crypto2 = require('crypto');
|
const crypto2 = require('crypto');
|
||||||
const Fs = require('fs');
|
|
||||||
const Path = require('path');
|
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const CDP = require('chrome-remote-interface');
|
const CDP = require('chrome-remote-interface');
|
||||||
const {} = require('child_process');
|
const {} = require('child_process');
|
||||||
const { dirname } = require('path');
|
|
||||||
const { fileURLToPath } = require('url');
|
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
import { GBLog } from 'botlib';
|
import { GBLog } from 'botlib';
|
||||||
|
import { CollectionUtil } from 'pragmatismo-io-framework';
|
||||||
import { GBServer } from '../../../../src/app';
|
import { GBServer } from '../../../../src/app';
|
||||||
const genericPool = require('generic-pool');
|
|
||||||
const finalStream = require('final-stream');
|
const finalStream = require('final-stream');
|
||||||
|
|
||||||
const waitUntil = (condition) => {
|
const waitUntil = condition => {
|
||||||
if (condition()) {
|
if (condition()) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (!condition()) {
|
if (!condition()) {
|
||||||
return;
|
return;
|
||||||
|
@ -30,89 +26,141 @@ const waitUntil = (condition) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const createVm2Pool = ({ min, max, ...limits }) => {
|
const createVm2Pool = ({ min, max, ...limits }) => {
|
||||||
limits = Object.assign({
|
limits = Object.assign(
|
||||||
|
{
|
||||||
cpu: 100,
|
cpu: 100,
|
||||||
memory: 2000,
|
memory: 2000,
|
||||||
time: 4000
|
time: 4000
|
||||||
}, limits);
|
},
|
||||||
|
limits
|
||||||
|
);
|
||||||
|
|
||||||
let limitError = null;
|
let limitError = null;
|
||||||
|
|
||||||
const ref = crypto2.randomBytes(20).toString('hex');
|
const ref = crypto2.randomBytes(20).toString('hex');
|
||||||
|
|
||||||
const kill = (x) => {
|
const kill = x => {
|
||||||
spawn('sh', ['-c', `pkill -9 -f ${ref}`]);
|
spawn('sh', ['-c', `pkill -9 -f ${ref}`]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let stderrCache = '';
|
let stderrCache = '';
|
||||||
const factory = {
|
|
||||||
create: function () {
|
|
||||||
|
|
||||||
const runner = spawn('cpulimit', [
|
const run = async (code, scope) => {
|
||||||
'-ql', limits.cpu,
|
const childProcess = spawn(
|
||||||
|
'cpulimit',
|
||||||
|
[
|
||||||
|
'-ql',
|
||||||
|
limits.cpu,
|
||||||
'--',
|
'--',
|
||||||
'node', `--inspect-brk=${limits.debuggerPort}`, `--experimental-fetch`, `--max-old-space-size=${limits.memory}`,
|
'node',
|
||||||
limits.script
|
`--inspect=${limits.debuggerPort}`,
|
||||||
, ref
|
`--experimental-fetch`,
|
||||||
], { cwd: limits.cwd, shell: false });
|
`--max-old-space-size=${limits.memory}`,
|
||||||
|
limits.script,
|
||||||
|
ref
|
||||||
|
],
|
||||||
|
{ cwd: limits.cwd, shell: false }
|
||||||
|
);
|
||||||
|
|
||||||
runner.stdout.on('data', (data) => {
|
childProcess.stdout.on('data', data => {
|
||||||
runner.socket = runner.socket || data.toString().trim();
|
childProcess.socket = childProcess.socket || data.toString().trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
runner.stderr.on('data', (data) => {
|
childProcess.stderr.on('data', data => {
|
||||||
stderrCache = stderrCache + data.toString();
|
stderrCache = stderrCache + data.toString();
|
||||||
if (stderrCache.includes('failed: address already in use'))
|
if (stderrCache.includes('failed: address already in use')) {
|
||||||
{
|
|
||||||
limitError = stderrCache;
|
limitError = stderrCache;
|
||||||
|
kill(process);
|
||||||
}
|
}
|
||||||
if (stderrCache.includes('FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory')) {
|
if (stderrCache.includes('FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory')) {
|
||||||
limitError = 'code execution exceeed allowed memory';
|
limitError = 'code execution exceeed allowed memory';
|
||||||
|
kill(process);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return runner;
|
let socket = null;
|
||||||
},
|
|
||||||
|
|
||||||
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);
|
await waitUntil(() => childProcess.socket);
|
||||||
|
socket = net.createConnection(childProcess.socket);
|
||||||
|
socket.write(JSON.stringify({ code, scope }) + '\n');
|
||||||
|
|
||||||
const socket = net.createConnection(childProcess.socket);
|
// Only attach if called by /debugger/run.
|
||||||
|
|
||||||
CDP(async (client) => {
|
if (GBServer.globals.debuggers[limits.botId]) {
|
||||||
|
|
||||||
|
|
||||||
|
const debug = async () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
CDP(async client => {
|
||||||
const { Debugger, Runtime } = client;
|
const { Debugger, Runtime } = client;
|
||||||
try {
|
try {
|
||||||
GBServer.globals.debuggers[scope.botId] = client;
|
GBServer.globals.debuggers[limits.botId].client = client;
|
||||||
|
await client.Runtime.runIfWaitingForDebugger();
|
||||||
|
await client.Debugger.enable();
|
||||||
|
|
||||||
|
async function mainScript({ Debugger }) {
|
||||||
|
return new Promise((fulfill, reject) => {
|
||||||
|
Debugger.scriptParsed(params => {
|
||||||
|
const { scriptId, url } = params;
|
||||||
|
fulfill(scriptId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await CollectionUtil.asyncForEach(GBServer.globals.debuggers[limits.botId].breaks, async brk => {
|
||||||
|
const scriptId = await mainScript(client);
|
||||||
|
const { breakpointId } = await client.Debugger.setBreakpoint({
|
||||||
|
location: {
|
||||||
|
scriptId,
|
||||||
|
lineNumber: brk
|
||||||
|
}
|
||||||
|
});
|
||||||
|
GBLog.info(`BASIC break defined ${breakpointId} for ${limits.botId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.Debugger.paused(({ callFrames, reason, hitBreakpoints }) => {
|
||||||
|
const frame = callFrames[0];
|
||||||
|
if (hitBreakpoints.length > 1) {
|
||||||
|
GBLog.info(`.gbdialog break at line ${frame.location.lineNumber + 1}`); // (zero-based)
|
||||||
|
|
||||||
|
const scope = `${frame.scopeChain[0].name} ${frame.scopeChain[0].object}`;
|
||||||
|
|
||||||
|
GBServer.globals.debuggers[limits.botId].scope = scope;
|
||||||
|
GBServer.globals.debuggers[limits.botId].state = 2;
|
||||||
|
} else if (reason === '') {
|
||||||
|
GBLog.info(`.gbdialog ${reason} at line ${frame.location.lineNumber + 1}`); // (zero-based)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
resolve(1);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
GBLog.error(err);
|
GBLog.error(err);
|
||||||
} finally {
|
kill(childProcess);
|
||||||
client.close();
|
|
||||||
}
|
}
|
||||||
}).on('error', (err) => {
|
}).on('error', err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
kill(childProcess);
|
||||||
|
reject(err);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
await debug();
|
||||||
|
}
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
limitError = 'code execution took too long and was killed';
|
limitError = 'code execution took too long and was killed';
|
||||||
kill(childProcess);
|
kill(childProcess);
|
||||||
}, limits.time);
|
}, limits.time);
|
||||||
|
|
||||||
socket.write(JSON.stringify({ code, scope }) + '\n');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data = await finalStream(socket);
|
let data = await finalStream(socket);
|
||||||
|
|
||||||
data = JSON.parse(data)
|
data = JSON.parse(data);
|
||||||
|
if (!data.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
throw new Error(data.error);
|
throw new Error(data.error);
|
||||||
}
|
}
|
||||||
|
@ -121,16 +169,13 @@ const createVm2Pool = ({ min, max, ...limits }) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(limitError || error);
|
throw new Error(limitError || error);
|
||||||
} finally {
|
} finally {
|
||||||
|
kill(childProcess);
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
pool.destroy(childProcess);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run,
|
run
|
||||||
drain: () => {
|
|
||||||
pool.drain().then(() => pool.clear());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,7 @@ export class GBServer {
|
||||||
GBServer.globals.minInstances = [];
|
GBServer.globals.minInstances = [];
|
||||||
GBServer.globals.wwwroot = null;
|
GBServer.globals.wwwroot = null;
|
||||||
GBServer.globals.entryPointDialog = null;
|
GBServer.globals.entryPointDialog = null;
|
||||||
|
GBServer.globals.debuggers = [];
|
||||||
|
|
||||||
server.use(bodyParser.json());
|
server.use(bodyParser.json());
|
||||||
server.use(bodyParser.urlencoded({ extended: true }));
|
server.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
|
Loading…
Add table
Reference in a new issue