Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix high cpu usage #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
45 changes: 28 additions & 17 deletions src/stepper.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
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) {
if (this.destroyed) {
throw 'stepper-destroyed';
}

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

try {
await this.currentStep;
Expand All @@ -32,7 +29,7 @@ class Stepper {
return;
}

this.pausePromise = wait();
this.pausePromise = wait(Infinity);
}

async resume() {
Expand All @@ -56,10 +53,6 @@ class Stepper {
return this.events.once(event, handler);
}

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

async destroy() {
this.events.destroy();
this.destroyed = true;
Expand All @@ -76,21 +69,39 @@ class Stepper {

module.exports = Stepper;

function wait(ms = false) {
function wait(ms) {
if (!ms) {
return;
}

let rejector;
let resolver;

const promise = new Promise((resolve, reject) => {
rejector = reject;
resolver = resolve;

if (ms !== false) {
setTimeout(resolve, ms);
if (ms !== Infinity) {
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
34 changes: 8 additions & 26 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 @@ -101,7 +101,7 @@ describe('interpreter', function () {
const execution = run(code);
const { promises } = execution;
const { executionEnd } = promises;
setTimeout(() => execution.pause(), 10);
execution.pause();
setTimeout(() => execution.resume(), 200);
await expect(execution).to.eventually.be.fulfilled;
await expect(executionEnd).to.eventually.be.fulfilled;
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