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