Skip to content

Commit

Permalink
fix high cpu usage
Browse files Browse the repository at this point in the history
  • Loading branch information
FranciscoCaetano88 committed Nov 25, 2020
1 parent d8238e1 commit f7d0c48
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 141 deletions.
16 changes: 5 additions & 11 deletions src/code-transforms.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const Babel = require('@babel/standalone');
const generate = require('@babel/generator');

exports.toES2015 = toES2015;
exports.prepare = prepare;
Expand Down Expand Up @@ -68,8 +67,7 @@ function stepInjector(babel) {
path.node.body = t.blockStatement([
createContextCall(
babel,
'step',
generate.default(path.node).code
'step'
)
]);
path.skip();
Expand Down Expand Up @@ -116,22 +114,19 @@ function stepInjector(babel) {
};
}

function createContextCall(babel, fnName, expr) {
function createContextCall(babel, fnName) {
const { types: t } = babel;

const stepperName = t.identifier(fnName);
const stepperArgs = [
t.templateLiteral([t.templateElement({ raw: expr })], [])
];

return t.expressionStatement(
t.awaitExpression(t.callExpression(stepperName, stepperArgs))
t.awaitExpression(t.callExpression(stepperName, []))
);
}

function prependContextCall(babel, path) {
return path.insertBefore(
createContextCall(babel, 'step', generate.default(path.node).code)
createContextCall(babel, 'step')
);
}

Expand All @@ -146,8 +141,7 @@ function implicitToExplicitReturnFunction(babel, path) {

const stepCall = createContextCall(
babel,
'step',
generate.default(path.node.body).code
'step'
);
const returnStatement = t.returnStatement(path.node.body);
const body = t.blockStatement([stepCall, returnStatement]);
Expand Down
1 change: 0 additions & 1 deletion src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const createContext = ({ events, userContext, stepper }) => {
},
pause: () => stepper.pause(),
resume: () => stepper.resume(),
setStepTime: (ms) => stepper.setStepTime(ms),
activeHandlers
},
...userContext
Expand Down
2 changes: 0 additions & 2 deletions src/execution-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const createExecutionController = ({ execution, events, context }) => {
stop,
pause,
resume,
setStepTime
} = context._execution;

const controller = {
Expand All @@ -15,7 +14,6 @@ const createExecutionController = ({ execution, events, context }) => {
stop,
pause,
resume,
setStepTime,
context,
promises: {
get executionEnd() {
Expand Down
3 changes: 1 addition & 2 deletions src/interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const Stepper = require('./stepper');

const run = (code = '', options = {}) => {
const {
stepTime = 15,
on = {},
context: userContext = {},
es2015 = false,
Expand All @@ -17,7 +16,7 @@ const run = (code = '', options = {}) => {
} = options;
const events = EventEmitter();

const stepper = new Stepper({ stepTime });
const stepper = new Stepper();
const context = createContext({ events, userContext, stepper });
const vm = new VM(context);

Expand Down
65 changes: 36 additions & 29 deletions src/stepper.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
const EventEmitter = require('./event-emitter');

class Stepper {
constructor(options = {}) {
const { stepTime = 100 } = options;

constructor() {
this.events = EventEmitter();
this.stepTime = stepTime;
}

async step(expr) {
async step(ms = false) {
if (this.destroyed) {
throw 'stepper-destroyed';
}

this.events.emit('step', expr);
this.currentStep = wait(this.stepTime);
this.events.emit('step');

if (!this.paused && ms === false) {
return;
}

this.currentStep = wait(ms);

try {
await this.currentStep;

if (this.pausePromise) {
await this.pausePromise;
}
} catch (err) {
throw 'stepper-destroyed';
}
}

async pause() {
if (this.pausePromise) {
pause() {
if (this.paused) {
return;
}

this.pausePromise = wait();
this.paused = true;
}

async resume() {
if (!this.pausePromise) {
resume() {
if (!this.paused) {
return;
}

this.pausePromise.resolve();
this.pausePromise = null;
this.paused = false;
if (this.currentStep) {
this.currentStep.resolve();
}
}

on(event, handler) {
Expand All @@ -56,21 +56,14 @@ class Stepper {
return this.events.once(event, handler);
}

setStepTime(stepTime) {
this.stepTime = stepTime;
}

async destroy() {
this.events.destroy();
this.destroyed = true;
this.paused = false;

if (this.currentStep) {
this.currentStep.cancel();
}

if (this.pausePromise) {
this.pausePromise.cancel();
}
}
}

Expand All @@ -85,12 +78,26 @@ function wait(ms = false) {
resolver = resolve;

if (ms !== false) {
setTimeout(resolve, ms);
setTimeout(() => promise.resolve(), ms);
}
});

promise.cancel = () => rejector('destroyed');
promise.resolve = () => resolver();
const destroy = () => {
rejector = null;
resolver = null;
};
promise.cancel = () => {
if (rejector) {
rejector('destroyed');
}
destroy();
};
promise.resolve = () => {
if (resolver) {
resolver();
}
destroy();
};

return promise;
}
22 changes: 11 additions & 11 deletions test/code-transforms.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,27 +63,27 @@ describe('code-transforms', function () {
describe('step injection', function () {
it('should inject steps before declarations', function () {
const input = `const a = 1;`;
const step = 'await step(`const a = 1;`);';
const step = 'await step();';
expect(prepare(input)).to.include(step);
});
it('should inject steps before declarations inside functions', function () {
const input = `function test() { const a = 1; }`;
const step = 'await step(`const a = 1;`);';
const step = 'await step();';
expect(prepare(input)).to.include(step);
});
it('should inject steps before declarations inside for loops', function () {
const input = `for(let i = 0; i < 1; i++) { const a = 1; }`;
const step = 'await step(`const a = 1;`);';
const step = 'await step();';
expect(prepare(input)).to.include(step);
});
it('should inject steps before declarations inside while loops', function () {
const input = `while(true) { const a = 1; }`;
const step = 'await step(`const a = 1;`);';
const step = 'await step();';
expect(prepare(input)).to.include(step);
});
it('should inject step call with the next block code as argument (without that block steps)', function () {
it('should inject multiple steps', function () {
const input = `while (true) { console.log('hey!'); }`;
const output = /await step\(`while \(true\) {\s+console\.log\('hey!'\);\s+}`\)/s;
const output = /await step\(\)/s;
expect(prepare(input)).to.match(output);
});
it('should inject step calls inside anonymous callback functions', async function () {
Expand All @@ -93,7 +93,7 @@ describe('code-transforms', function () {
console.log(element);
});
`;
const step = 'await step(`console.log(element);`)';
const step = 'await step()';
expect(prepare(input)).to.include(step);
});
it('should inject step calls inside arrow functions with body', async function () {
Expand All @@ -105,8 +105,8 @@ describe('code-transforms', function () {
});
`;

const step1 = 'await step(`console.log(element);`)';
const step2 = 'await step(`return element + 1;`)';
const step1 = 'await step()';
const step2 = 'await step()';
expect(prepare(input)).to.include(step1);
expect(prepare(input)).to.include(step2);
});
Expand All @@ -116,7 +116,7 @@ describe('code-transforms', function () {
const b = a.map(element => element + 1);
`;

const step = 'await step(`element + 1`);';
const step = 'await step();';
expect(prepare(input)).to.include(step);
});
it('should inject step calls inside with blocks', async function () {
Expand All @@ -126,7 +126,7 @@ describe('code-transforms', function () {
}
`;

const step = 'await step(`const a = [1, 2];`);';
const step = 'await step();';
expect(prepare(input)).to.include(step);
});
});
Expand Down
32 changes: 7 additions & 25 deletions test/interpreter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ describe('interpreter', function () {
'stop',
'resume',
'promises',
'setStepTime',
'on',
'emit',
'once',
Expand All @@ -62,6 +61,7 @@ describe('interpreter', function () {
it('run() promises should be fulfilled when user stops execution', async function () {
const code = `
while(true) {
step(1);
const a = 1;
}
`;
Expand Down Expand Up @@ -152,44 +152,26 @@ describe('interpreter', function () {
expect(callback).to.have.been.callCount(5);
});

it('should be protected against infinite while loops', async function () {
it('should be protected against infinite while loops with explicit step', async function () {
const code = `
while(true) {}
while(true) { step(1); }
`;

const execution = run(code);
setTimeout(() => execution.stop(), 200);
await expect(execution).to.eventually.be.fulfilled;
});

it('should be protected against infinite for loops', async function () {
it('should be protected against infinite for loops with explicit step', async function () {
const code = `
for (;;) {}
for (;;) { step(1); }
`;

const execution = run(code);
setTimeout(() => execution.stop(), 200);
await expect(execution).to.eventually.be.fulfilled;
});

it('should be able to control step time while running', async function () {
const INITIAL_STEP_TIME = 100;

const code = `
for (let i = 0; i < 2; i++) {
const a = 1;
}
`;

const before = performance.now();
const execution = run(code, { stepTime: INITIAL_STEP_TIME });
setTimeout(() => execution.setStepTime(1), 10);
await expect(execution).to.eventually.be.fulfilled;
const after = performance.now();

expect(before - after).to.be.lessThan(INITIAL_STEP_TIME * 2);
});

it('should be able to execute code in parallel', async function () {
const logger = sinon.fake();
const parallel = sinon.spy((...fns) =>
Expand Down Expand Up @@ -280,12 +262,12 @@ describe('interpreter', function () {
await run(code, { on: { start: callback } });
expect(callback).to.have.been.called;
});
it('should fire on.step event with next expression', async function () {
it('should fire on.step event', async function () {
const callback = sinon.fake();
const code = `const firstStep = 1;`;

await run(code, { on: { step: callback } });
expect(callback).to.have.been.calledWith('const firstStep = 1;');
expect(callback).to.have.been.called;
});
it('should call on.step event 2 times', async function () {
const callback = sinon.fake();
Expand Down
Loading

0 comments on commit f7d0c48

Please sign in to comment.