new(all): Alpha Word Debugger for 3.0.

This commit is contained in:
rodrigorodriguez 2022-11-12 21:33:45 -03:00
parent 79f77b528d
commit 301de1f73c
4 changed files with 145 additions and 127 deletions

View file

@ -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 }) {
GBLog.info(`BASIC: Running ${botId} in DEBUG mode.`);
GBServer.globals.debuggers[botId].state = 1;
this.debugMap[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');
} }
} }

View file

@ -770,7 +770,7 @@ export class GBVMService extends GBService {
// Creates a class DialogKeywords which is the *this* pointer // Creates a class DialogKeywords which is the *this* pointer
// in BASIC. // in BASIC.
const user = step ? await min.userProfile.get(step.context, {}) : null; const user = step ? await min.userProfile.get(step.context, {}) : null;
const sandbox = { user: user.systemUser }; const sandbox = { user: user.systemUser };
@ -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,
@ -840,8 +840,7 @@ 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}`);

View file

@ -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, {
memory: 2000, cpu: 100,
time: 4000 memory: 2000,
}, limits); time: 4000
},
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', [
'-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 run = async (code, scope) => {
const childProcess = await pool.acquire(); const childProcess = spawn(
'cpulimit',
[
'-ql',
limits.cpu,
'--',
'node',
`--inspect=${limits.debuggerPort}`,
`--experimental-fetch`,
`--max-old-space-size=${limits.memory}`,
limits.script,
ref
],
{ cwd: limits.cwd, shell: false }
);
childProcess.stdout.on('data', data => {
childProcess.socket = childProcess.socket || data.toString().trim();
});
childProcess.stderr.on('data', data => {
stderrCache = stderrCache + data.toString();
if (stderrCache.includes('failed: address already in use')) {
limitError = stderrCache;
kill(process);
}
if (stderrCache.includes('FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory')) {
limitError = 'code execution exceeed allowed memory';
kill(process);
}
});
let socket = null;
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 { Debugger, Runtime } = client;
try {
GBServer.globals.debuggers[scope.botId] = client; const debug = async () => {
} catch (err) { return new Promise((resolve, reject) => {
GBLog.error(err); CDP(async client => {
} finally { const { Debugger, Runtime } = client;
client.close(); try {
} GBServer.globals.debuggers[limits.botId].client = client;
}).on('error', (err) => { await client.Runtime.runIfWaitingForDebugger();
console.error(err); 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) {
GBLog.error(err);
kill(childProcess);
}
}).on('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());
}
}; };
}; };

View file

@ -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 }));