Refactor code execution to use async functions

This refactoring is required for Sentry tracing. It ensures that the respective functions only return as soon as a code execution finished. With this approach, we can then instrument the duration of the functions, so that Sentry spans are created as desired.

Co-authored-by: Jan Graichen <jgraichen@altimos.de>
This commit is contained in:
Sebastian Serth
2024-05-24 12:53:11 +02:00
committed by Sebastian Serth
parent c8609e5392
commit 86c67f3c9a
6 changed files with 205 additions and 158 deletions

View File

@ -3,7 +3,7 @@ CodeOceanEditorWebsocket = {
// Replace `http` with `ws` for the WebSocket connection. This also works with `https` and `wss`.
webSocketProtocol: window.location.protocol.replace(/^http/, 'ws').split(':')[0],
initializeSocket: function(urlHelper, params, closeCallback) {
runSocket: function(urlHelper, params, setupFunction) {
// 1. Specify the protocol for all URLs to generate
params.protocol = this.webSocketProtocol;
params._options = true;
@ -32,48 +32,73 @@ CodeOceanEditorWebsocket = {
// 4. Connect to the given URL.
this.websocket = new CommandSocket(url,
function (evt) {
this.resetOutputTab();
}.bind(this)
function (evt) {
this.resetOutputTab();
}.bind(this)
);
// Attach custom handlers for messages received.
setupFunction(this.websocket);
CodeOceanEditorWebsocket.websocket = this.websocket;
this.websocket.onError(this.showWebsocketError.bind(this));
this.websocket.onClose(function(span, callback){
span?.finish();
if(callback != null){
callback();
}
}.bind(this, span, closeCallback));
// Create and return a new Promise. It will only resolve (or fail) once the connection has ended.
return new Promise((resolve, reject) => {
this.websocket.onError(this.showWebsocketError.bind(this));
// Remove event listeners for Promise handling.
// This is especially useful in case of an error, where a `close` event might follow the `error` event.
const teardown = () => {
this.websocket.websocket.removeEventListener(closeListener);
this.websocket.websocket.removeEventListener(errorListener);
};
// We are using event listeners (and not `onError` or `onClose`) here, since these listeners should never be overwritten.
// With `onError` or `onClose`, a new assignment would overwrite a previous one.
const closeListener = this.websocket.websocket.addEventListener('close', () => {
span?.finish();
resolve();
teardown();
});
const errorListener = this.websocket.websocket.addEventListener('error', (error) => {
reject(error);
teardown();
this.websocket.killWebSocket(); // In case of error, ensure we always close the connection.
});
});
},
initializeSocketForTesting: function(submissionID, filename) {
this.initializeSocket(Routes.test_submission_url, {id: submissionID, filename: filename});
this.websocket.on('default',this.handleTestResponse.bind(this));
this.websocket.on('exit', this.handleExitCommand.bind(this));
socketTestCode: function(submissionID, filename) {
return this.runSocket(Routes.test_submission_url, {id: submissionID, filename: filename}, (websocket) => {
websocket.on('default', this.handleTestResponse.bind(this));
websocket.on('exit', this.handleExitCommand.bind(this));
});
},
initializeSocketForScoring: function(submissionID) {
this.initializeSocket(Routes.score_submission_url, {id: submissionID}, function() {
$('#assess').one('click', this.scoreCode.bind(this))
}.bind(this));
this.websocket.on('default',this.handleScoringResponse.bind(this));
this.websocket.on('hint', this.showHint.bind(this));
this.websocket.on('exit', this.handleExitCommand.bind(this));
this.websocket.on('status', this.showStatus.bind(this));
socketScoreCode: function(submissionID) {
return this.runSocket(Routes.score_submission_url, {id: submissionID}, (websocket) => {
websocket.on('default', this.handleScoringResponse.bind(this));
websocket.on('hint', this.showHint.bind(this));
websocket.on('exit', this.handleExitCommand.bind(this));
websocket.on('status', this.showStatus.bind(this));
}).then(() => {
$('#assess').one('click', this.scoreCode.bind(this));
});
},
initializeSocketForRunning: function(submissionID, filename) {
this.initializeSocket(Routes.run_submission_url, {id: submissionID, filename: filename});
this.websocket.on('input',this.showPrompt.bind(this));
this.websocket.on('write', this.printWebsocketOutput.bind(this));
this.websocket.on('clear', this.clearOutput.bind(this));
this.websocket.on('turtle', this.handleTurtleCommand.bind(this));
this.websocket.on('turtlebatch', this.handleTurtlebatchCommand.bind(this));
this.websocket.on('render', this.printWebsocketOutput.bind(this));
this.websocket.on('exit', this.handleExitCommand.bind(this));
this.websocket.on('status', this.showStatus.bind(this));
this.websocket.on('hint', this.showHint.bind(this));
this.websocket.on('files', this.prepareFileDownloads.bind(this));
socketRunCode: function(submissionID, filename) {
return this.runSocket(Routes.run_submission_url, {id: submissionID, filename: filename}, (websocket) => {
websocket.on('input', this.showPrompt.bind(this));
websocket.on('write', this.printWebsocketOutput.bind(this));
websocket.on('clear', this.clearOutput.bind(this));
websocket.on('turtle', this.handleTurtleCommand.bind(this));
websocket.on('turtlebatch', this.handleTurtlebatchCommand.bind(this));
websocket.on('render', this.printWebsocketOutput.bind(this));
websocket.on('exit', this.handleExitCommand.bind(this));
websocket.on('status', this.showStatus.bind(this));
websocket.on('hint', this.showHint.bind(this));
websocket.on('files', this.prepareFileDownloads.bind(this));
});
},
handleExitCommand: function() {