diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js index 838b17a2..6d505efc 100644 --- a/app/assets/javascripts/editor.js +++ b/app/assets/javascripts/editor.js @@ -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, "
"); 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})); diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index b246de80..b7940ed9 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -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 diff --git a/lib/docker_client.rb b/lib/docker_client.rb index 1c0de144..3562da13 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -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) :