Ensure that only one EventMachine is running
This commit is contained in:

committed by
Sebastian Serth

parent
5cc180d0e9
commit
c7369366d5
@ -11,12 +11,13 @@ class Runner::Connection
|
||||
|
||||
attr_writer :status
|
||||
|
||||
def initialize(url, strategy)
|
||||
def initialize(url, strategy, event_loop)
|
||||
@socket = Faye::WebSocket::Client.new(url, [], ping: 5)
|
||||
@strategy = strategy
|
||||
@status = :established
|
||||
@event_loop = event_loop
|
||||
|
||||
# For every event type of faye websockets, the corresponding
|
||||
# For every event type of Faye WebSockets, the corresponding
|
||||
# RunnerConnection method starting with `on_` is called.
|
||||
%i[open message error close].each do |event_type|
|
||||
@socket.on(event_type) {|event| __send__(:"on_#{event_type}", event) }
|
||||
@ -41,6 +42,17 @@ class Runner::Connection
|
||||
@socket.send(encoded_message)
|
||||
end
|
||||
|
||||
def close(status)
|
||||
return unless active?
|
||||
|
||||
@status = status
|
||||
@socket.close
|
||||
end
|
||||
|
||||
def active?
|
||||
@status == :established
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def decode(_raw_event)
|
||||
@ -61,7 +73,7 @@ class Runner::Connection
|
||||
if WEBSOCKET_MESSAGE_TYPES.include?(message_type)
|
||||
__send__("handle_#{message_type}", event)
|
||||
else
|
||||
raise Runner::Error::UnexpectedResponse.new("Unknown websocket message type: #{message_type}")
|
||||
raise Runner::Error::UnexpectedResponse.new("Unknown WebSocket message type: #{message_type}")
|
||||
end
|
||||
end
|
||||
|
||||
@ -78,6 +90,9 @@ class Runner::Connection
|
||||
raise Runner::Error::ExecutionTimeout.new('Execution exceeded its time limit')
|
||||
when :terminated_by_codeocean, :terminated_by_management
|
||||
@exit_callback.call @exit_code
|
||||
@event_loop.stop
|
||||
when :terminated_by_client
|
||||
@event_loop.stop
|
||||
else # :established
|
||||
# If the runner is killed by the DockerContainerPool after the maximum allowed time per user and
|
||||
# while the owning user is running an execution, the command execution stops and log output is incomplete.
|
||||
|
22
lib/runner/event_loop.rb
Normal file
22
lib/runner/event_loop.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# EventLoop is an abstraction around Ruby's queue so that its usage is better
|
||||
# understandable in our context.
|
||||
class Runner::EventLoop
|
||||
def initialize
|
||||
@queue = Queue.new
|
||||
end
|
||||
|
||||
# wait waits until another thread calls stop on this EventLoop.
|
||||
# There may only be one active wait call per loop at a time, otherwise it is not
|
||||
# deterministic which one will be unblocked if stop is called.
|
||||
def wait
|
||||
@queue.pop
|
||||
end
|
||||
|
||||
# stop unblocks the currently active wait call. If there is none, the
|
||||
# next call to wait will not be blocking.
|
||||
def stop
|
||||
@queue.push nil if @queue.empty?
|
||||
end
|
||||
end
|
@ -51,19 +51,22 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy
|
||||
raise Runner::Error::FaradayError.new("Request to DockerContainerPool failed: #{e.inspect}")
|
||||
end
|
||||
|
||||
def attach_to_execution(command)
|
||||
def attach_to_execution(command, event_loop)
|
||||
@command = command
|
||||
query_params = 'logs=0&stream=1&stderr=1&stdout=1&stdin=1'
|
||||
websocket_url = "#{self.class.config[:ws_host]}/v1.27/containers/#{@container_id}/attach/ws?#{query_params}"
|
||||
|
||||
EventMachine.run do
|
||||
socket = Connection.new(websocket_url, self)
|
||||
EventMachine.add_timer(@execution_environment.permitted_execution_time) do
|
||||
socket.status = :timeout
|
||||
destroy_at_management
|
||||
socket = Connection.new(websocket_url, self, event_loop)
|
||||
begin
|
||||
Timeout.timeout(@execution_environment.permitted_execution_time) do
|
||||
socket.send(command)
|
||||
yield(socket)
|
||||
event_loop.wait
|
||||
event_loop.stop
|
||||
end
|
||||
socket.send(command)
|
||||
yield(socket)
|
||||
rescue Timeout::Error
|
||||
socket.close(:timeout)
|
||||
destroy_at_management
|
||||
end
|
||||
end
|
||||
|
||||
@ -118,8 +121,7 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy
|
||||
# Assume correct termination for now and return exit code 0
|
||||
# TODO: Can we use the actual exit code here?
|
||||
@exit_code = 0
|
||||
@status = :terminated_by_codeocean
|
||||
@socket.close
|
||||
close(:terminated_by_codeocean)
|
||||
when /#{format(@strategy.execution_environment.test_command, class_name: '.*', filename: '.*', module_name: '.*')}/
|
||||
# TODO: Super dirty hack to redirect test output to stderr (remove attr_reader afterwards)
|
||||
@stream = 'stderr'
|
||||
|
@ -85,12 +85,10 @@ class Runner::Strategy::Poseidon < Runner::Strategy
|
||||
raise Runner::Error::FaradayError.new("Request to Poseidon failed: #{e.inspect}")
|
||||
end
|
||||
|
||||
def attach_to_execution(command)
|
||||
def attach_to_execution(command, event_loop)
|
||||
websocket_url = execute_command(command)
|
||||
EventMachine.run do
|
||||
socket = Connection.new(websocket_url, self)
|
||||
yield(socket)
|
||||
end
|
||||
socket = Connection.new(websocket_url, self, event_loop)
|
||||
yield(socket)
|
||||
end
|
||||
|
||||
def destroy_at_management
|
||||
@ -113,7 +111,7 @@ class Runner::Strategy::Poseidon < Runner::Strategy
|
||||
if websocket_url.present?
|
||||
return websocket_url
|
||||
else
|
||||
raise Runner::Error::UnexpectedResponse.new('Poseidon did not send websocket url')
|
||||
raise Runner::Error::UnexpectedResponse.new('Poseidon did not send a WebSocket URL')
|
||||
end
|
||||
when 400
|
||||
Runner.destroy(@allocation_id)
|
||||
@ -132,7 +130,7 @@ class Runner::Strategy::Poseidon < Runner::Strategy
|
||||
def decode(raw_event)
|
||||
JSON.parse(raw_event.data)
|
||||
rescue JSON::ParserError => e
|
||||
raise Runner::Error::UnexpectedResponse.new("The websocket message from Poseidon could not be decoded to JSON: #{e.inspect}")
|
||||
raise Runner::Error::UnexpectedResponse.new("The WebSocket message from Poseidon could not be decoded to JSON: #{e.inspect}")
|
||||
end
|
||||
|
||||
def encode(data)
|
||||
|
Reference in New Issue
Block a user