diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index f5a59054..947d7c1c 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -137,6 +137,45 @@ class SubmissionsController < ApplicationController end end + def handle_websockets(tubesock, container) + socket = container.socket + tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => :container_running}) + @waiting_for_container_time = Time.zone.now - @container_request_time + @execution_request_time = Time.zone.now + + socket.on :message do |event| + Rails.logger.info("#{Time.zone.now.getutc}: Docker sending: #{event.data}") + handle_message(event.data, tubesock) + end + + socket.on :close do |_event| + EventMachine.stop_event_loop + tubesock.send_data JSON.dump({'cmd' => 'timeout'}) if container.status == 'timeouted' + kill_socket(tubesock) + end + + tubesock.onmessage do |data| + Rails.logger.info("#{Time.now.getutc.to_s}: Client sending: #{data}") + # Check whether the client send a JSON command and kill container + # if the command is 'client_kill', send it to docker otherwise. + begin + + parsed = JSON.parse(data) unless data == "\n" + if parsed.instance_of?(Hash) && parsed['cmd'] == 'client_kill' + Rails.logger.debug("Client exited container.") + container.destroy + else + socket.send data + Rails.logger.debug { "Sent the received client data to docker:#{data}" } + end + rescue JSON::ParserError => error + socket.send data + Rails.logger.debug { "Rescued parsing error, sent the received client data to docker:#{data}" } + Sentry.set_extras(data: data) + end + end + end + def run Thread.new do hijack do |tubesock| @@ -144,46 +183,13 @@ class SubmissionsController < ApplicationController kill_socket(tubesock) return end - EventMachine.run do - container_request_time = Time.zone.now - @submission.run(sanitize_filename) do |socket| - tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => :container_running}) - @waiting_for_container_time = Time.zone.now - container_request_time - @execution_request_time = Time.zone.now - - socket.on :message do |event| - Rails.logger.info("#{Time.zone.now.getutc}: Docker sending: #{event.data}") - handle_message(event.data, tubesock) - end - - socket.on :close do |_event| - EventMachine.stop_event_loop - kill_socket(tubesock) - end - - tubesock.onmessage do |data| - Rails.logger.info(Time.now.getutc.to_s + ": Client sending: " + data) - # Check whether the client send a JSON command and kill container - # if the command is 'client_kill', send it to docker otherwise. - begin - - parsed = JSON.parse(data) unless data == "\n" - if parsed.instance_of?(Hash) && parsed['cmd'] == 'client_kill' - Rails.logger.debug("Client exited container.") - container.destroy - else - socket.send data - Rails.logger.debug { "Sent the received client data to docker:#{data}" } - end - rescue JSON::ParserError => error - socket.send data - Rails.logger.debug { "Rescued parsing error, sent the received client data to docker:#{data}" } - Sentry.set_extras(data: data) - end - end - end + @container_request_time = Time.zone.now + @submission.run(sanitize_filename) do |container| + handle_websockets(tubesock, container) end end + ensure + ActiveRecord::Base.connection_pool.release_connection end # unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? # Thread.new do @@ -396,20 +402,19 @@ class SubmissionsController < ApplicationController def statistics; end def test - hijack do |tubesock| - unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? - Thread.new do - EventMachine.run - ensure - ActiveRecord::Base.connection_pool.release_connection + Thread.new do + hijack do |tubesock| + if @embed_options[:disable_run] + kill_socket(tubesock) + return + end + @container_request_time = Time.now + @submission.run_tests(sanitize_filename) do |container| + handle_websockets(tubesock, container) end end - - output = @docker_client.execute_test_command(@submission, sanitize_filename) - - # tubesock is the socket to the client - tubesock.send_data JSON.dump(output) - tubesock.send_data JSON.dump('cmd' => 'exit') + ensure + ActiveRecord::Base.connection_pool.release_connection end end diff --git a/app/models/submission.rb b/app/models/submission.rb index fc05be7e..757c4786 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -136,24 +136,36 @@ class Submission < ApplicationRecord end end - def test(file) + def score(file) score_command = command_for execution_environment.test_command, file container = run_command_with_self score_command container + # Todo receive websocket data and pass it to some score function end - def run(file) + def run(file, &block) run_command = command_for execution_environment.run_command, file - container = run_command_with_self run_command - container - yield(container.socket) if block_given? + execute_interactively(run_command, &block) + end + + def run_tests(file, &block) + test_command = command_for execution_environment.test_command, file + execute_interactively(test_command, &block) + end + + def execute_interactively(command) + container = nil + EventMachine.run do + container = run_command_with_self command + yield(container) if block_given? + end container.destroy end def run_command_with_self(command) container = Container.new(execution_environment, execution_environment.permitted_execution_time) container.copy_submission_files self - container.execute_command_interactively(command) + container.execute_interactively(command) container end diff --git a/lib/container.rb b/lib/container.rb index 140a50ad..ee1add52 100644 --- a/lib/container.rb +++ b/lib/container.rb @@ -37,16 +37,19 @@ class Container response end - def execute_command_interactively(command) + def execute_interactively(command) websocket_url = execute_command(command)[:websocket_url] @socket = Faye::WebSocket::Client.new(websocket_url, [], ping: 0.1) - # Faye::WebSocket::Client.new(socket_url, [], headers: headers, ping: 0.1) end def destroy Faraday.delete container_url end + def status + parse(Faraday.get(container_url))[:status] + end + private def container_url