lib/faastruby/server/runner.rb in faastruby-0.4.18 vs lib/faastruby/server/runner.rb in faastruby-0.5.0

- old
+ new

@@ -1,43 +1,138 @@ require 'base64' - +require 'open3' module FaaStRuby + # require 'faastruby/server/response' + # require 'faastruby/server/runner_methods' + # require 'faastruby/server/function_object' class Runner - include RunnerMethods - def initialize + def initialize(function_name) + # puts "initializing runner for function name #{function_name}" @rendered = false + @function_name = function_name + @function_folder = "#{FaaStRuby::ProjectConfig.functions_dir}/#{function_name}" + # puts "function_folder: #{@function_folder}" + @config_file = "#{@function_folder}/faastruby.yml" + # puts "reading config file #{@config_file}" + @config = YAML.load(File.read(@config_file)) + @language, @version = (@config['runtime'] || DEFAULT_RUBY_RUNTIME).split(':') end - def path - @path - end - def load_function(path) - eval "Module.new do; #{File.read(path)};end" + eval %( + Module.new do + def self.require(path) + return load("\#{path}.rb") if File.file?("\#{path}.rb") + Kernel.require path + end + #{File.read(path)} + end + ) end - def call(workspace_name, function_name, event, args) - @short_path = "#{workspace_name}/#{function_name}" - @path = "#{FaaStRuby::PROJECT_ROOT}/#{workspace_name}/#{function_name}" - short_path = "#{workspace_name}/#{function_name}" + def call(event, args) begin - Dir.chdir(@path) - function = load_function("#{@path}/handler.rb") - runner = FunctionObject.new(short_path) - runner.extend(function) - response = runner.handler(event, *args) - # response = handler(event, args) - return response if response.is_a?(FaaStRuby::Response) + case @language + when 'ruby' + time, response = call_ruby(event, args) + when 'crystal' + time, response = call_crystal(event, args) + else + puts "[Runner] ERROR: could not determine the language for function #{@function_name}.".red + end + return [time, response] if response.is_a?(FaaStRuby::Response) body = { 'error' => "Please use the helpers 'render' or 'respond_with' as your function return value." } - FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'}) + [time, FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})] rescue Exception => e + STDOUT.puts e.full_message body = { 'error' => e.message, 'location' => e.backtrace&.first, } - FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'}) + [0.0, FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})] end + end + def call_ruby(event, args) + function_object = FunctionObject.new(@function_name) + reader, writer = IO.pipe + time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + pid = fork do + # puts "loading #{@function_folder}/handler.rb" + Dir.chdir(@function_folder) + begin + function = load_function("#{@function_folder}/handler.rb") + function_object.extend(function) + response = function_object.handler(event, *args) + raise FaaStRuby::Response::InvalidResponseError unless response.is_a?(FaaStRuby::Response) + rescue Exception => e + error = Oj.dump({ + 'error' => e.message, + 'location' => e.backtrace&.first + }) + response = FaaStRuby::Response.error(error) + end + writer.puts response.payload + writer.close + exit 0 + end + response = FaaStRuby::Response.from_payload reader.gets.chomp + reader.close + Process.wait(pid) + time_finish = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + time = (time_finish - time_start).round(2) + [time, response] + end + + def chdir + CHDIR_MUTEX.synchronize do + # puts "Switching to directory #{@function_folder}" + Dir.chdir(@function_folder) + yield + end + end + + def call_crystal(event, args) + #### + # This is a hack to address the bug https://github.com/crystal-lang/crystal/issues/7052 + event.query_params.each do |k, v| + event.query_params[k] = '' if v.nil? + end + event.headers.each do |k, v| + event.headers[k] = '' if v.nil? + end + #### + payload_json = Oj.dump({'event' => event.to_h, 'args' => []}) + payload = Base64.urlsafe_encode64(payload_json, padding: false) + cmd = "./handler" + # STDOUT.puts "Running #{cmd}" + # STDOUT.puts "From #{@function_folder}" + output = nil + time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + Open3.popen2(cmd, chdir: @function_folder) do |stdin, stdout, status| + stdin.puts payload + stdout.each_line do |line| + if line[0..1] == 'R,' + output = line.chomp + break + else + puts line.chomp + end + end + end + time_finish = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + time = (time_finish - time_start).round(2) + tag, o = output.split(',') + decoded_response = Base64.urlsafe_decode64(o) + response_obj = Oj.load(decoded_response) + # STDOUT.puts response_obj + response = FaaStRuby::Response.new( + body: response_obj['response'], + status: response_obj['status'], + headers: response_obj['headers'], + binary: response_obj['binary'] + ) + [time, response] end end end \ No newline at end of file