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 37649216..7ccff39d 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/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb
index a1a804f1..e56c73c7 100644
--- a/app/controllers/submissions_controller.rb
+++ b/app/controllers/submissions_controller.rb
@@ -112,8 +112,8 @@ class SubmissionsController < ApplicationController
begin
parsed = JSON.parse(data)
if parsed['cmd'] == 'exit'
- Rails.logger.info("Client killed container.")
- @docker_client.kill_container(result[:container])
+ Rails.logger.info("Client exited container.")
+ @docker_client.exit_container(result[:container])
else
socket.send data
end
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
diff --git a/lib/docker_client.rb b/lib/docker_client.rb
index 3562da13..594d0bd2 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)
@@ -125,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)
@@ -135,6 +138,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 +153,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 +174,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 +275,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 +294,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)