diff --git a/Gemfile b/Gemfile index 3b1b2cd2..5269fe34 100644 --- a/Gemfile +++ b/Gemfile @@ -5,8 +5,8 @@ gem 'bcrypt', '~> 3.1.7' gem 'bootstrap-will_paginate' gem 'carrierwave' gem 'coffee-rails', '~> 4.0.0' -gem 'concurrent-ruby' -gem 'concurrent-ruby-ext', platform: :ruby +gem 'concurrent-ruby', '~> 1.0.0' +gem 'concurrent-ruby-ext', '~> 1.0.0', platform: :ruby gem 'docker-api','~> 1.21.1', require: 'docker' gem 'factory_girl_rails', '~> 4.0' gem 'forgery' @@ -19,9 +19,9 @@ gem 'kramdown' gem 'newrelic_rpm' gem 'pg', platform: :ruby gem 'pry' -gem 'puma' +gem 'puma', '~> 2.15.3' gem 'pundit' -gem 'rails', '~> 4.1.2' +gem 'rails', '~> 4.1.13' gem 'rails-i18n', '~> 4.0.0' gem 'ransack' gem 'rubytree' diff --git a/Gemfile.lock b/Gemfile.lock index 0492a3e1..5972ea79 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,32 +2,32 @@ GEM remote: https://rubygems.org/ specs: ZenTest (4.11.0) - actionmailer (4.1.10) - actionpack (= 4.1.10) - actionview (= 4.1.10) + actionmailer (4.1.14) + actionpack (= 4.1.14) + actionview (= 4.1.14) mail (~> 2.5, >= 2.5.4) - actionpack (4.1.10) - actionview (= 4.1.10) - activesupport (= 4.1.10) + actionpack (4.1.14) + actionview (= 4.1.14) + activesupport (= 4.1.14) rack (~> 1.5.2) rack-test (~> 0.6.2) - actionview (4.1.10) - activesupport (= 4.1.10) + actionview (4.1.14) + activesupport (= 4.1.14) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.10) - activesupport (= 4.1.10) + activemodel (4.1.14) + activesupport (= 4.1.14) builder (~> 3.1) - activerecord (4.1.10) - activemodel (= 4.1.10) - activesupport (= 4.1.10) + activerecord (4.1.14) + activemodel (= 4.1.14) + activesupport (= 4.1.14) arel (~> 5.0.0) activerecord-jdbc-adapter (1.3.15) activerecord (>= 2.2) activerecord-jdbcpostgresql-adapter (1.3.15) activerecord-jdbc-adapter (~> 1.3.15) jdbc-postgres (>= 9.1) - activesupport (4.1.10) + activesupport (4.1.14) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) @@ -95,11 +95,10 @@ GEM execjs coffee-script-source (1.9.1) colorize (0.7.7) - concurrent-ruby (0.8.0) - ref (~> 1.0, >= 1.0.5) - concurrent-ruby (0.8.0-java) - concurrent-ruby-ext (0.8.0) - concurrent-ruby (~> 0.8.0) + concurrent-ruby (1.0.0) + concurrent-ruby (1.0.0-java) + concurrent-ruby-ext (1.0.0) + concurrent-ruby (~> 1.0.0) database_cleaner (1.4.1) debug_inspector (0.0.2) diff-lcs (1.2.5) @@ -141,17 +140,17 @@ GEM jquery-turbolinks (2.1.0) railties (>= 3.1.0) turbolinks - json (1.8.2) - json (1.8.2-java) + json (1.8.3) + json (1.8.3-java) jwt (1.4.1) kramdown (1.6.0) mail (2.6.3) mime-types (>= 1.16, < 3) method_source (0.8.2) - mime-types (2.4.3) + mime-types (2.99) mini_portile (0.6.2) - minitest (5.5.1) - multi_json (1.11.0) + minitest (5.8.3) + multi_json (1.11.2) multi_xml (0.5.5) multipart-post (2.0.0) net-scp (1.2.1) @@ -185,31 +184,29 @@ GEM method_source (~> 0.8.1) slop (~> 3.4) spoon (~> 0.0) - puma (2.11.1) - rack (>= 1.1, < 2.0) - puma (2.11.1-java) - rack (>= 1.1, < 2.0) + puma (2.15.3) + puma (2.15.3-java) pundit (0.3.0) activesupport (>= 3.0.0) - rack (1.5.2) + rack (1.5.5) rack-test (0.6.3) rack (>= 1.0) - rails (4.1.10) - actionmailer (= 4.1.10) - actionpack (= 4.1.10) - actionview (= 4.1.10) - activemodel (= 4.1.10) - activerecord (= 4.1.10) - activesupport (= 4.1.10) + rails (4.1.14) + actionmailer (= 4.1.14) + actionpack (= 4.1.14) + actionview (= 4.1.14) + activemodel (= 4.1.14) + activerecord (= 4.1.14) + activesupport (= 4.1.14) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.10) + railties (= 4.1.14) sprockets-rails (~> 2.0) rails-i18n (4.0.4) i18n (~> 0.6) railties (~> 4.0) - railties (4.1.10) - actionpack (= 4.1.10) - activesupport (= 4.1.10) + railties (4.1.14) + actionpack (= 4.1.14) + activesupport (= 4.1.14) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.0.0) @@ -221,7 +218,6 @@ GEM i18n polyamorous (~> 1.2) rdoc (4.2.0) - ref (1.0.5) rspec (3.1.0) rspec-core (~> 3.1.0) rspec-expectations (~> 3.1.0) @@ -286,12 +282,12 @@ GEM spoon (0.0.4) ffi spring (1.3.4) - sprockets (2.12.3) + sprockets (2.12.4) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.2.4) + sprockets-rails (2.3.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) @@ -351,8 +347,8 @@ DEPENDENCIES carrierwave codeclimate-test-reporter coffee-rails (~> 4.0.0) - concurrent-ruby - concurrent-ruby-ext + concurrent-ruby (~> 1.0.0) + concurrent-ruby-ext (~> 1.0.0) database_cleaner docker-api (~> 1.21.1) factory_girl_rails (~> 4.0) @@ -368,9 +364,9 @@ DEPENDENCIES nyan-cat-formatter pg pry - puma + puma (~> 2.15.3) pundit - rails (~> 4.1.2) + rails (~> 4.1.13) rails-i18n (~> 4.0.0) rake ransack diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js index f67d1fb5..4c9b834e 100644 --- a/app/assets/javascripts/editor.js +++ b/app/assets/javascripts/editor.js @@ -100,12 +100,12 @@ $(function() { } }; - var confirmSubmission = function(event) { - event.preventDefault(); - if (confirm($(this).data('message-confirm'))) { - submitCode(); - } - }; + //var confirmSubmission = function(event) { + // event.preventDefault(); + // if (confirm($(this).data('message-confirm'))) { + // submitCode(); + // } + //}; var createSubmission = function(initiator, filter, callback) { showSpinner(initiator); @@ -637,7 +637,8 @@ $(function() { var initializeWorkflowButtons = function() { $('#start').on('click', showWorkspaceTab); - $('#submit').on('click', confirmSubmission); + //$('#submit').on('click', confirmSubmission); + $('#submit').on('click', submitCode); }; var initializeWorkspaceButtons = function() { diff --git a/app/assets/javascripts/turtle.js b/app/assets/javascripts/turtle.js index 8aa8a9fe..f2fa184b 100644 --- a/app/assets/javascripts/turtle.js +++ b/app/assets/javascripts/turtle.js @@ -33,6 +33,7 @@ function Turtle(pipe, canvas) { 'x': xpos - dx, 'y': ypos - dy })); + pipe.send('\n'); }); } diff --git a/app/views/application/help.html.slim b/app/views/application/help.html.slim index 50e72507..22c3f955 100644 --- a/app/views/application/help.html.slim +++ b/app/views/application/help.html.slim @@ -1,9 +1,6 @@ - unless local_assigns[:modal] h1 = t('shared.help.headline') -h2 = t('shared.help.general_help') -== Forgery(:lorem_ipsum).paragraphs(10, html: true) - - if local_assigns.has_key?(:execution_environment) h2 = t('shared.help.execution_environment_specific_help', execution_environment: execution_environment) = render_markdown(execution_environment.help) diff --git a/app/views/exercises/implement.html.slim b/app/views/exercises/implement.html.slim index a9ce3a54..6ede6d68 100644 --- a/app/views/exercises/implement.html.slim +++ b/app/views/exercises/implement.html.slim @@ -76,8 +76,7 @@ br - if session[:lti_parameters].try(:has_key?, 'lis_outcome_service_url') - p.text-center = render('editor_button', classes: 'btn-lg btn-success', data: {:'data-message-confirm' => t('exercises.editor.confirm_submit'), :'data-url' => submit_exercise_path(@exercise)}, icon: 'fa fa-send', id: 'submit', label: t('exercises.editor.submit')) - + p.text-center = render('editor_button', classes: 'btn-lg btn-success', data: {:'data-url' => submit_exercise_path(@exercise)}, icon: 'fa fa-send', id: 'submit', label: t('exercises.editor.submit')) - if qa_url #questions-column #questions-holder data-url="#{qa_url}/qa/index/#{@exercise.id}/#{@user_id}" diff --git a/config/application.rb b/config/application.rb index 744438a9..4b2a543a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -33,6 +33,8 @@ module CodeOcean case (RUBY_ENGINE) when 'ruby' # ... + #this is enabled in prod for testing + config.middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement when 'jruby' # plattform specific java.lang.Class.for_name('javax.crypto.JceSecurity').get_declared_field('isRestricted').tap{|f| f.accessible = true; f.set nil, false} diff --git a/config/docker.yml.erb b/config/docker.yml.erb index 9f916acc..5ac309e8 100644 --- a/config/docker.yml.erb +++ b/config/docker.yml.erb @@ -18,8 +18,8 @@ production: active: true refill: async: false - batch_size: 32 - interval: 30 + batch_size: 8 + interval: 15 timeout: 60 workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %> ws_host: ws://localhost:4243 diff --git a/lib/docker_client.rb b/lib/docker_client.rb index 654c6504..77bc40c6 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -88,6 +88,8 @@ class DockerClient def self.create_container(execution_environment) tries ||= 0 + Rails.logger.info "docker_client: self.create_container with creation options:" + Rails.logger.info(container_creation_options(execution_environment)) container = Docker::Container.create(container_creation_options(execution_environment)) local_workspace_path = generate_local_workspace_path # container.start always creates the passed local_workspace_path on disk. Seems like we have to live with that, therefore we can also just create the empty folder ourselves. @@ -97,7 +99,7 @@ class DockerClient container.status = :created container rescue Docker::Error::NotFoundError => error - Rails.logger.info('create_container: Got Docker::Error::NotFoundError: ' + error) + Rails.logger.info('create_container: Got Docker::Error::NotFoundError: ' + error.to_s) destroy_container(container) #(tries += 1) <= RETRY_COUNT ? retry : raise(error) end @@ -113,6 +115,8 @@ class DockerClient create_workspace_file(container: container, file: file) end end + rescue Docker::Error::NotFoundError => error + Rails.logger.info('create_workspace_files: Rescued from Docker::Error::NotFoundError: ' + error.to_s) end private :create_workspace_files @@ -131,6 +135,9 @@ class DockerClient if(container) container.delete(force: true, v: true) end + rescue Docker::Error::NotFoundError => error + Rails.logger.error('destroy_container: Rescued from Docker::Error::NotFoundError: ' + error.to_s) + Rails.logger.error('No further actions are done concerning that.') end def execute_arbitrary_command(command, &block) @@ -297,7 +304,12 @@ class DockerClient def self.return_container(container, execution_environment) Rails.logger.debug('returning container ' + container.to_s) - clean_container_workspace(container) + begin + clean_container_workspace(container) + rescue Docker::Error::NotFoundError => error + Rails.logger.info('return_container: Rescued from Docker::Error::NotFoundError: ' + error.to_s) + Rails.logger.info('Nothing is done here additionally. The container will be exchanged upon its next retrieval.') + end DockerContainerPool.return_container(container, execution_environment) container.status = :returned end diff --git a/lib/docker_container_pool.rb b/lib/docker_container_pool.rb index d027076b..1d02876f 100644 --- a/lib/docker_container_pool.rb +++ b/lib/docker_container_pool.rb @@ -1,6 +1,6 @@ require 'concurrent/future' require 'concurrent/timer_task' -require 'concurrent/utilities' + class DockerContainerPool @@ -18,10 +18,19 @@ class DockerContainerPool @config ||= CodeOcean::Config.new(:docker).read(erb: true)[:pool] end + def self.containers + @containers + end + + def self.all_containers + @all_containers + end + def self.remove_from_all_containers(container, execution_environment) @all_containers[execution_environment.id]-=[container] if(@containers[execution_environment.id].include?(container)) @containers[execution_environment.id]-=[container] + Rails.logger.debug('Removed container ' + container.to_s + ' from all_pool for execution environment ' + execution_environment.to_s + '. Remaining containers in all_pool ' + @all_containers[execution_environment.id].size.to_s) end end @@ -29,6 +38,7 @@ class DockerContainerPool @all_containers[execution_environment.id]+=[container] if(!@containers[execution_environment.id].include?(container)) @containers[execution_environment.id]+=[container] + Rails.logger.debug('Added container ' + container.to_s + ' to all_pool for execution environment ' + execution_environment.to_s + '. Containers in all_pool: ' + @all_containers[execution_environment.id].size.to_s) else Rails.logger.info('failed trying to add existing container ' + container.to_s + ' to execution_environment ' + execution_environment.to_s) end @@ -57,9 +67,28 @@ class DockerContainerPool Rails.logger.debug('get_container fetched container ' + container.to_s + ' for execution environment ' + execution_environment.to_s) # just access and the following if we got a container. Otherwise, the execution_environment might be just created and not fully exist yet. if(container) - Rails.logger.debug('get_container remaining avail. containers: ' + @containers[execution_environment.id].size.to_s) - Rails.logger.debug('get_container all container count: ' + @all_containers[execution_environment.id].size.to_s) + begin + # check whether the container is running. exited containers go to the else part. + # Dead containers raise a NotFOundError on the container.json call. This is handled in the rescue block. + if(container.json['State']['Running']) + Rails.logger.debug('get_container remaining avail. containers: ' + @containers[execution_environment.id].size.to_s) + Rails.logger.debug('get_container all container count: ' + @all_containers[execution_environment.id].size.to_s) + else + Rails.logger.error('docker_container_pool.get_container retrieved a container not running. Container will be removed from list: ' + container.to_s) + remove_from_all_containers(container, execution_environment) + Rails.logger.error('Creating a new container and returning that.') + container = create_container(execution_environment) + DockerContainerPool.add_to_all_containers(container, execution_environment) + end + rescue Docker::Error::NotFoundError => error + Rails.logger.error('docker_container_pool.get_container rescued from Docker::Error::NotFoundError. Most likely, the container is not there any longer. Removing faulty entry from list: ' + container.to_s) + remove_from_all_containers(container, execution_environment) + Rails.logger.error('Creating a new container and returning that.') + container = create_container(execution_environment) + DockerContainerPool.add_to_all_containers(container, execution_environment) + end end + # returning nil is no problem. then the pool is just depleted. container else create_container(execution_environment) @@ -71,7 +100,7 @@ class DockerContainerPool end def self.refill - ExecutionEnvironment.where('pool_size > 0').each do |execution_environment| + ExecutionEnvironment.where('pool_size > 0').order(pool_size: :desc).each do |execution_environment| if config[:refill][:async] Concurrent::Future.execute { refill_for_execution_environment(execution_environment) } else @@ -85,18 +114,19 @@ class DockerContainerPool if refill_count > 0 Rails.logger.info('Adding ' + refill_count.to_s + ' containers for execution_environment ' + execution_environment.name ) c = refill_count.times.map { create_container(execution_environment) } - Rails.logger.debug('Created containers: ' + c.to_s ) + Rails.logger.info('Created containers: ' + c.to_s ) + #c.each { |container| return_container(container, execution_environment) } @containers[execution_environment.id] += c @all_containers[execution_environment.id] += c - Rails.logger.debug('@containers ' + @containers.object_id.to_s + ' has:'+ @containers[execution_environment.id].to_s) - Rails.logger.debug('@all_containers ' + @containers.object_id.to_s + ' has:'+ @all_containers[execution_environment.id].to_s) + Rails.logger.debug('@containers for ' + execution_environment.name.to_s + ' (' + @containers.object_id.to_s + ') has the following content: '+ @containers[execution_environment.id].to_s) + Rails.logger.debug('@all_containers for ' + execution_environment.name.to_s + ' (' + @all_containers.object_id.to_s + ') has the following content: ' + @all_containers[execution_environment.id].to_s) #refill_count.times.map { create_container(execution_environment) } end end def self.start_refill_task - @refill_task = Concurrent::TimerTask.new(execution_interval: config[:refill][:interval], run_now: false, timeout_interval: config[:refill][:timeout]) { refill } + @refill_task = Concurrent::TimerTask.new(execution_interval: config[:refill][:interval], run_now: true, timeout_interval: config[:refill][:timeout]) { refill } @refill_task.execute end end