arepl-backend
Version:
JS interface to python evaluator for AREPL
180 lines • 7.75 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PythonExecutors = void 0;
const python_shell_1 = require("python-shell");
const pythonExecutor_1 = require("./pythonExecutor");
__exportStar(require("./pythonExecutor"), exports);
/**
* Starts multiple python executors for running user code.
* Will manage them for you, so you can treat this class
* as a single executor.
*/
class PythonExecutors {
constructor(options = {}) {
this.options = options;
this.executors = [];
this.currentExecutorIndex = 0;
/**
* delays execution of function by ms milliseconds, resetting clock every time it is called
* Useful for real-time execution so execCode doesn't get called too often
* thanks to https://stackoverflow.com/a/1909508/6629672
*/
this.debounce = (function () {
let timer = 0;
return function (callback, ms, ...args) {
clearTimeout(timer);
timer = setTimeout(callback, ms, args);
};
})();
}
start(numExecutors = 3) {
// we default to three executors, as it should be enough so that there is always
// one available to accept incoming code
if (this.executors.length != 0)
throw Error('already started!');
for (let i = 0; i < numExecutors; i++) {
console.log('starting executor ' + i.toString());
const pyExecutor = new pythonExecutor_1.PythonExecutor(this.options);
pyExecutor.start(() => { });
pyExecutor.evaluatorName = i.toString();
pyExecutor.onResult = result => {
// Other executor may send a result right before it dies
// So we use this function to only capture result from active executor
if (i == this.currentExecutorIndex)
this.onResult(result);
};
pyExecutor.onPrint = print => {
if (i == this.currentExecutorIndex)
this.onPrint(print);
};
pyExecutor.onStderr = stderr => {
if (i == this.currentExecutorIndex)
this.onStderr(stderr);
};
pyExecutor.pyshell.on('error', this.onError);
pyExecutor.pyshell.childProcess.on('exit', exitCode => {
if (exitCode != 0)
this.onAbnormalExit(exitCode);
});
this.executors.push(pyExecutor);
}
}
/**
* Sends code to the current executor.
* If current executor is busy, nothing happens
*/
execCodeCurrent(code) {
this.executors[this.currentExecutorIndex].execCode(code);
}
/**
* sends code to a free executor to be executed
* Side-effect: restarts dirty executors
*/
execCode(code) {
// old code is now irrelevant, if we are still waiting to send old code
// we should stop waiting
clearInterval(this.waitForFreeExecutor);
// this timeout should definitely not still be going on, but we clear it just in case
clearTimeout(this.wait_for_other_runs_to_complete);
let last_run_still_executing = false;
if (this.executors.some(executor => executor.state == pythonExecutor_1.PythonState.Executing)) {
last_run_still_executing = true;
}
// executors running old code are now irrelevant, restart them
this.executors.filter(executor => executor.state == pythonExecutor_1.PythonState.Executing || executor.state == pythonExecutor_1.PythonState.DirtyFree)
.forEach(executor => executor.restart());
if (last_run_still_executing) {
// wait for last run to complete
// we don't want to run two programs at once
// which could cause a race condition
this.wait_for_other_runs_to_complete = setTimeout(this.exec_when_free_executor.bind(this, code), pythonExecutor_1.PythonExecutor.GRACE_PERIOD + 5);
}
else {
this.exec_when_free_executor(code);
}
}
exec_when_free_executor(code) {
let freeExecutor = this.executors.find(executor => executor.state == pythonExecutor_1.PythonState.FreshFree);
if (!freeExecutor) {
this.waitForFreeExecutor = setInterval(() => {
freeExecutor = this.executors.find(executor => executor.state == pythonExecutor_1.PythonState.FreshFree);
if (freeExecutor) {
freeExecutor.execCode(code);
this.currentExecutorIndex = parseInt(freeExecutor.evaluatorName);
clearInterval(this.waitForFreeExecutor);
}
}, 60);
}
else {
freeExecutor.execCode(code);
this.currentExecutorIndex = parseInt(freeExecutor.evaluatorName);
}
}
stop(kill_immediately = false) {
clearInterval(this.waitForFreeExecutor);
this.executors.forEach(executor => executor.stop(kill_immediately));
this.executors = [];
}
/**
* checks syntax without executing code
* @param {string} code
* @returns {Promise} rejects w/ stderr if syntax failure
*/
checkSyntax(code) {
return __awaiter(this, void 0, void 0, function* () {
return python_shell_1.PythonShell.checkSyntax(code);
});
}
/**
* Overwrite this with your own handler.
* is called when active executor fails or completes
*/
onResult(foo) { }
/**
* Overwrite this with your own handler.
* Is called when active executor prints
* @param {string} foo
*/
onPrint(foo) { }
/**
* Overwrite this with your own handler.
* Is called when active executor logs stderr
* @param {string} foo
*/
onStderr(foo) { }
/**
* Overwrite this with your own handler.
* Is called when there is a Node.JS error event with the python process
* The 'error' event is emitted whenever:
The process could not be spawned, or
The process could not be killed, or
Sending a message to the child process failed.
*/
onError(err) { }
onAbnormalExit(exitCode) { }
}
exports.PythonExecutors = PythonExecutors;
//# sourceMappingURL=pythonExecutors.js.map