From 6151afcb55cefb9a7161b53e3e8dded60b3c6c78 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 16 Oct 2015 17:06:23 +0200 Subject: [PATCH 1/5] some I18n, stop command for server send event code (score), updated readme --- README.md | 7 ++++--- app/assets/javascripts/editor.js | 28 ++++++++++++++++++++++++- app/views/exercises/implement.html.slim | 4 ++-- config/locales/de.yml | 2 ++ config/locales/en.yml | 2 ++ 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f62ea4be..0da65662 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ In order to execute code submissions using Docker, source code files are written - create *config/sendmail.yml* - create *config/smtp.yml* -- use boot2docker if there is no native support for docker on your OS +- use boot2docker or vagrant if there is no native support for docker on your OS - create seed data by executing `rake db:seed` +- if you already created a configuration for your local installation and want to use vagrant, too, be sure to log into the vagrant instance via ssh and add your database user manually to the database. Afterwards, create, migrate and seed. ## Production Setup @@ -46,8 +47,8 @@ The application is compatible with MRI and JRuby. Due to superior parallelism, w 1.1 - [ ] WebSocket Suppport - [ ] Interactive Exercises + [x] WebSocket Suppport + [x] Interactive Exercises [ ] Allow Disabling of File Creation [ ] Set Container Recyling per Environment diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js index 160cf315..45182b3d 100644 --- a/app/assets/javascripts/editor.js +++ b/app/assets/javascripts/editor.js @@ -13,6 +13,9 @@ $(function() { var THEME = 'ace/theme/textmate'; var REMEMBER_TAB = false; var AUTOSAVE_INTERVAL = 15 * 1000; + var NONE = 0; + var WEBSOCKET = 1; + var SERVER_SEND_EVENT = 2; var editors = []; var active_file = undefined; @@ -20,6 +23,7 @@ $(function() { var running = false; var qa_api = undefined; var output_mode_is_streaming = true; + var runmode = NONE; var websocket, turtlescreen, @@ -873,6 +877,7 @@ $(function() { var runCode = function(event) { event.preventDefault(); if ($('#run').is(':visible')) { + runmode = WEBSOCKET; createSubmission(this, null, function(response) { $('#stop').data('url', response.stop_url); running = true; @@ -910,6 +915,7 @@ $(function() { var scoreCode = function(event) { event.preventDefault(); + runmode = SERVER_SEND_EVENT; createSubmission(this, null, function(response) { showSpinner($('#assess')); var url = response.score_url; @@ -1018,10 +1024,30 @@ $(function() { var stopCode = function(event) { event.preventDefault(); if ($('#stop').is(':visible')) { - killWebsocketAndContainer(); + if(runmode == WEBSOCKET){ + killWebsocketAndContainer(); + } else if (runmode == SERVER_SEND_EVENT) { + stopCodeServerSendEvent(event); + } + runmode = NONE; } }; + var stopCodeServerSendEvent = function(event){ + 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 killWebsocketAndContainer = function() { if (websocket.readyState != WebSocket.OPEN) { return; diff --git a/app/views/exercises/implement.html.slim b/app/views/exercises/implement.html.slim index 72ac1b2c..80193cac 100644 --- a/app/views/exercises/implement.html.slim +++ b/app/views/exercises/implement.html.slim @@ -39,10 +39,10 @@ #output-col1 // todo set to full width if turtle isnt used #prompt.input-group.hidden - span.input-group-addon = 'Your input' + span.input-group-addon = t('exercises.editor.input') input#prompt-input.form-control type='text' span.input-group-btn - button#prompt-submit.btn.btn-primary type="button" = 'Send' + button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send') #output pre = t('.no_output_yet') - if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] diff --git a/config/locales/de.yml b/config/locales/de.yml index 9cb546b3..aaedd678 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -176,6 +176,7 @@ de: destroy_file: Datei löschen download: Herunterladen dummy: Keine Aktion + input: Ihre Eingabe lastsaved: 'Zuletzt gespeichert: ' network: 'Während Ihr Code läuft, ist Port %{port} unter folgender Adresse erreichbar: %{address}.' render: Anzeigen @@ -185,6 +186,7 @@ de: requestComments: Kommentare erbitten save: Speichern score: Bewerten + send: Senden start_over: Von vorne anfangen stop: Stoppen submit: Code zur Bewertung abgeben diff --git a/config/locales/en.yml b/config/locales/en.yml index d7fd84a9..5baed464 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -176,6 +176,7 @@ en: destroy_file: Delete File download: Download dummy: No Action + input: Your input lastsaved: 'Last saved: ' network: 'While your code is running, port %{port} is accessible using the following address: %{address}.' render: Render @@ -185,6 +186,7 @@ en: requestComments: Request comments save: Save score: Score + send: Send start_over: Start over stop: Stop submit: Submit Code For Assessment From 1360408dce75d378000d2122470bfb2ffbfc94c6 Mon Sep 17 00:00:00 2001 From: Janusch Jacoby Date: Mon, 19 Oct 2015 14:46:32 +0200 Subject: [PATCH 2/5] Fix container pooling with websockets --- lib/docker_client.rb | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/docker_client.rb b/lib/docker_client.rb index 3562da13..38c541d2 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -93,6 +93,7 @@ class DockerClient FileUtils.mkdir(local_workspace_path) container.start(container_start_options(execution_environment, local_workspace_path)) container.start_time = Time.now + container.status = :created container rescue Docker::Error::NotFoundError => error destroy_container(container) @@ -135,6 +136,7 @@ class DockerClient def execute_command(command, before_execution_block, output_consuming_block) #tries ||= 0 @container = DockerContainerPool.get_container(@execution_environment) + @container.status = :executing if @container before_execution_block.try(:call) send_command(command, @container, &output_consuming_block) @@ -149,6 +151,7 @@ class DockerClient def execute_websocket_command(command, before_execution_block, output_consuming_block) @container = DockerContainerPool.get_container(@execution_environment) + @container.status = :executing if @container before_execution_block.try(:call) # todo catch exception if socket could not be created @@ -169,21 +172,24 @@ class DockerClient Thread.new do timeout = @execution_environment.permitted_execution_time.to_i # seconds sleep(timeout) - Rails.logger.info("Killing container after timeout of " + timeout.to_s + " seconds.") - kill_container(container) + if container.status != :returned + Rails.logger.info("Killing container after timeout of " + timeout.to_s + " seconds.") + kill_container(container) + end end end - def kill_container(container) - """ - 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) : + def exit_container(container) + # if we use pooling and recylce the containers, put it back. otherwise, destroy it. + (DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container) + end + + def kill_container(container) + # remove container from pool, then destroy it + if (DockerContainerPool.config[:active]) + DockerContainerPool.remove_from_all_containers(container, @execution_environment) + end - #destroy container self.class.destroy_container(container) # if we recylce containers, we start a fresh one @@ -267,6 +273,7 @@ class DockerClient def self.return_container(container, execution_environment) clean_container_workspace(container) DockerContainerPool.return_container(container, execution_environment) + container.status = :returned end #private :return_container @@ -285,7 +292,9 @@ class DockerClient Rails.logger.info('got timeout error for container ' + container.to_s) # remove container from pool, then destroy it - (DockerContainerPool.config[:active]) ? DockerContainerPool.remove_from_all_containers(container, @execution_environment) : + if (DockerContainerPool.config[:active]) + DockerContainerPool.remove_from_all_containers(container, @execution_environment) + end # destroy container self.class.destroy_container(container) From 0b9cda4f84c07801c076a3052d88f735ba7a950f Mon Sep 17 00:00:00 2001 From: Janusch Jacoby Date: Mon, 19 Oct 2015 15:13:42 +0200 Subject: [PATCH 3/5] Fix container pooling #2 --- app/controllers/submissions_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index a1a804f1..62fe6320 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -113,7 +113,7 @@ class SubmissionsController < ApplicationController parsed = JSON.parse(data) if parsed['cmd'] == 'exit' Rails.logger.info("Client killed container.") - @docker_client.kill_container(result[:container]) + @docker_client.exit_container(result[:container]) else socket.send data end From e88520f43bdb53482238c62c6cffe466402f64f8 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Mon, 19 Oct 2015 15:50:58 +0200 Subject: [PATCH 4/5] corrected log message. --- app/controllers/submissions_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 62fe6320..e56c73c7 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -112,7 +112,7 @@ class SubmissionsController < ApplicationController begin parsed = JSON.parse(data) if parsed['cmd'] == 'exit' - Rails.logger.info("Client killed container.") + Rails.logger.info("Client exited container.") @docker_client.exit_container(result[:container]) else socket.send data From 9e748e5dc9b1bde7fcff60111c658f5b5e7e3976 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Mon, 19 Oct 2015 15:52:48 +0200 Subject: [PATCH 5/5] prevent nil access on containers not present any longer in delete... --- lib/docker_client.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/docker_client.rb b/lib/docker_client.rb index 38c541d2..594d0bd2 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -126,7 +126,9 @@ class DockerClient container.stop.kill container.port_bindings.values.each { |port| PortPool.release(port) } clean_container_workspace(container) - container.delete(force: true, v: true) + if(container) + container.delete(force: true, v: true) + end end def execute_arbitrary_command(command, &block)