Improve error and output handling
Display websocket and container status messages as well as line feeds to the user.
This commit is contained in:
@ -25,7 +25,7 @@ $(function() {
|
||||
numMessages = 0,
|
||||
turtlecanvas = $('#turtlecanvas'),
|
||||
prompt = $('#prompt'),
|
||||
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit'],
|
||||
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'status'],
|
||||
streams = ['stdin', 'stdout', 'stderr'];
|
||||
|
||||
var ENTER_KEY_CODE = 13;
|
||||
@ -192,20 +192,14 @@ $(function() {
|
||||
|
||||
var evaluateCodeWithStreamedResponse = function(url, callback) {
|
||||
initWebsocketConnection(url);
|
||||
console.log(callback);
|
||||
|
||||
// TODO only init turtle when required
|
||||
initTurtle();
|
||||
|
||||
// TODO reimplement via websocket messsages
|
||||
/*var event_source = new EventSource(url);
|
||||
|
||||
event_source.addEventListener('close', closeEventSource);
|
||||
event_source.addEventListener('error', closeEventSource);
|
||||
event_source.addEventListener('hint', renderHint);
|
||||
event_source.addEventListener('info', storeContainerInformation);
|
||||
event_source.addEventListener('output', callback);
|
||||
event_source.addEventListener('start', callback);
|
||||
|
||||
if ($('#flowrHint').isPresent()) {
|
||||
event_source.addEventListener('output', handleStderrOutputForFlowr);
|
||||
@ -214,11 +208,7 @@ $(function() {
|
||||
|
||||
if (qa_api) {
|
||||
event_source.addEventListener('close', handleStreamedResponseForCodePilot);
|
||||
}
|
||||
|
||||
event_source.addEventListener('status', function(event) {
|
||||
showStatus(JSON.parse(event.data));
|
||||
});*/
|
||||
}*/
|
||||
};
|
||||
|
||||
var handleStreamedResponseForCodePilot = function(event) {
|
||||
@ -976,7 +966,7 @@ $(function() {
|
||||
if (output.status === 'timeout') {
|
||||
showTimeoutMessage();
|
||||
} else if (output.status === 'container_depleted') {
|
||||
showContainerDepletedMessage();
|
||||
showContainerDepletedMessage();
|
||||
} else if (output.stderr) {
|
||||
$.flash.danger({
|
||||
icon: ['fa', 'fa-bug'],
|
||||
@ -1010,6 +1000,12 @@ $(function() {
|
||||
});
|
||||
};
|
||||
|
||||
var showWebsocketError = function() {
|
||||
$.flash.danger({
|
||||
text: $('#flash').data('message-failure')
|
||||
});
|
||||
}
|
||||
|
||||
var showWorkspaceTab = function(event) {
|
||||
event.preventDefault();
|
||||
showTab(1);
|
||||
@ -1018,40 +1014,29 @@ $(function() {
|
||||
var stopCode = function(event) {
|
||||
event.preventDefault();
|
||||
if ($('#stop').is(':visible')) {
|
||||
killWebsocket();
|
||||
stopContainer();
|
||||
killWebsocketAndContainer();
|
||||
}
|
||||
};
|
||||
|
||||
// todo we are missing the url here
|
||||
// we could also hide the container completely by killing it on the server and only exposing the websocket
|
||||
var stopContainer = function() {
|
||||
var jqxhr = ajax({
|
||||
data: {
|
||||
container_id: $('#stop').data('container').id
|
||||
},
|
||||
url: $('#stop').data('url')
|
||||
});
|
||||
jqxhr.always(function() {
|
||||
hideSpinner();
|
||||
running = false;
|
||||
toggleButtonStates();
|
||||
});
|
||||
jqxhr.fail(ajaxError);
|
||||
}
|
||||
|
||||
var killWebsocket = function() {
|
||||
var killWebsocketAndContainer = function() {
|
||||
if (websocket.readyState != WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
// todo flash notification
|
||||
websocket.send(JSON.stringify({cmd: 'exit'}));
|
||||
websocket.flush();
|
||||
websocket.close();
|
||||
// todo remove this once xhr works or container is killed on the server
|
||||
hideSpinner();
|
||||
running = false;
|
||||
toggleButtonStates();
|
||||
hidePrompt();
|
||||
flashKillMessage();
|
||||
}
|
||||
|
||||
var flashKillMessage = function() {
|
||||
$.flash.info({
|
||||
icon: ['fa', 'fa-clock-o'],
|
||||
text: "Your program was stopped." // todo get data attribute
|
||||
});
|
||||
}
|
||||
|
||||
// todo set this from websocket command, required to e.g. stop container
|
||||
@ -1111,10 +1096,10 @@ $(function() {
|
||||
|
||||
var initWebsocketConnection = function(url) {
|
||||
websocket = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + url);
|
||||
websocket.onopen = function(evt) { onWebSocketOpen(evt) };
|
||||
websocket.onclose = function(evt) { onWebSocketClose(evt) };
|
||||
websocket.onmessage = function(evt) { onWebSocketMessage(evt) };
|
||||
websocket.onerror = function(evt) { onWebSocketError(evt) };
|
||||
websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection
|
||||
websocket.onclose = function(evt) { /* expected at some point */ };
|
||||
websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); };
|
||||
websocket.onerror = function(evt) { showWebsocketError(); };
|
||||
websocket.flush = function() { this.send('\n'); }
|
||||
};
|
||||
|
||||
@ -1136,51 +1121,42 @@ $(function() {
|
||||
}
|
||||
}
|
||||
|
||||
var onWebSocketOpen = function(evt) {
|
||||
resetOutputTab();
|
||||
};
|
||||
|
||||
var onWebSocketClose = function(evt) {
|
||||
// no reason to alert since this will happen either way
|
||||
};
|
||||
|
||||
var onWebSocketMessage = function(evt) {
|
||||
parseCanvasMessage(evt.data, true);
|
||||
};
|
||||
|
||||
var onWebSocketError = function(evt) {
|
||||
// todo flash error message
|
||||
};
|
||||
|
||||
var executeWebsocketCommand = function(msg) {
|
||||
if ($.inArray(msg.cmd, commands) == -1) {
|
||||
console.log("Unknown command: " + msg.cmd);
|
||||
// skipping unregistered commands is required
|
||||
// as we may receive mirrored response due to internal behaviour
|
||||
return;
|
||||
console.log("Unknown command: " + msg.cmd);
|
||||
// skipping unregistered commands is required
|
||||
// as we may receive mirrored response due to internal behaviour
|
||||
return;
|
||||
}
|
||||
switch(msg.cmd) {
|
||||
case 'input':
|
||||
showPrompt();
|
||||
break;
|
||||
case 'write':
|
||||
printWebsocketOutput(msg);
|
||||
break;
|
||||
case 'turtle':
|
||||
showCanvas();
|
||||
handleTurtleCommand(msg);
|
||||
break;
|
||||
case 'turtlebatch':
|
||||
showCanvas();
|
||||
handleTurtlebatchCommand(msg);
|
||||
break;
|
||||
case 'exit':
|
||||
killWebsocket();
|
||||
break;
|
||||
case 'input':
|
||||
showPrompt();
|
||||
break;
|
||||
case 'write':
|
||||
printWebsocketOutput(msg);
|
||||
break;
|
||||
case 'turtle':
|
||||
showCanvas();
|
||||
handleTurtleCommand(msg);
|
||||
break;
|
||||
case 'turtlebatch':
|
||||
showCanvas();
|
||||
handleTurtlebatchCommand(msg);
|
||||
break;
|
||||
case 'exit':
|
||||
killWebsocketAndContainer();
|
||||
break;
|
||||
case 'status':
|
||||
showStatus(msg)
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var printWebsocketOutput = function(msg) {
|
||||
if (!msg.data) {
|
||||
return;
|
||||
}
|
||||
msg.data = msg.data.replace(/(\r\n|\n|\r)/gm, "</br>");
|
||||
var stream = {};
|
||||
stream[msg.stream] = msg.data;
|
||||
printOutput(stream, true, 0);
|
||||
@ -1210,7 +1186,6 @@ $(function() {
|
||||
}
|
||||
|
||||
var submitPromptInput = function() {
|
||||
// todo make sure websocket is actually open
|
||||
var input = $('#prompt-input');
|
||||
var message = input.val();
|
||||
websocket.send(JSON.stringify({cmd: 'result', 'data': message}));
|
||||
|
@ -78,8 +78,6 @@ class SubmissionsController < ApplicationController
|
||||
# server_sent_event.write({stdout: output[:stdout]}, event: 'output') if output[:stdout]
|
||||
# server_sent_event.write({stderr: output[:stderr]}, event: 'output') if output[:stderr]
|
||||
|
||||
# server_sent_event.write({status: output[:status]}, event: 'status')
|
||||
|
||||
# unless output[:stderr].nil?
|
||||
# if hint = Whistleblower.new(execution_environment: @submission.execution_environment).generate_hint(output[:stderr])
|
||||
# server_sent_event.write(hint, event: 'hint')
|
||||
@ -93,11 +91,7 @@ class SubmissionsController < ApplicationController
|
||||
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
|
||||
|
||||
result = @docker_client.execute_run_command(@submission, params[:filename])
|
||||
|
||||
if result[:status] == :container_depleted
|
||||
tubesock.send_data JSON.dump({'cmd' => 'container_depleted'})
|
||||
kill_socket(tubesock)
|
||||
end
|
||||
tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => result[:status]})
|
||||
|
||||
if result[:status] == :container_running
|
||||
socket = result[:socket]
|
||||
@ -127,6 +121,8 @@ class SubmissionsController < ApplicationController
|
||||
socket.send data
|
||||
end
|
||||
end
|
||||
else
|
||||
kill_socket(tubesock)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -164,7 +164,7 @@ class DockerClient
|
||||
def kill_after_timeout(container)
|
||||
"""
|
||||
We need to start a second thread to kill the websocket connection,
|
||||
as it is impossible to determine when no more input is requested.
|
||||
as it is impossible to determine whether further input is requested.
|
||||
"""
|
||||
Thread.new do
|
||||
timeout = @execution_environment.permitted_execution_time.to_i # seconds
|
||||
@ -175,9 +175,11 @@ class DockerClient
|
||||
end
|
||||
|
||||
def kill_container(container)
|
||||
|
||||
# todo won't this always create a new container?
|
||||
# It does, because it's impossible to determine wether a programm is still running or not while using ws to run.
|
||||
"""
|
||||
Please note that we cannot properly recycle containers when using
|
||||
websockets because it is impossible to determine whether a program
|
||||
is still running.
|
||||
"""
|
||||
# remove container from pool, then destroy it
|
||||
(DockerContainerPool.config[:active]) ? DockerContainerPool.remove_from_all_containers(container, @execution_environment) :
|
||||
|
||||
|
Reference in New Issue
Block a user