# frozen_string_literal: true module SidekiqUniqueJobs module Script # Interface to dealing with .lua files # # @author Mikael Henriksson class Client include SidekiqUniqueJobs::Script::Timing # # @!attribute [r] logger # @return [Logger] an instance of a logger attr_reader :logger # # @!attribute [r] file_name # @return [String] The name of the file to execute attr_reader :config # # @!attribute [r] scripts # @return [Scripts] the collection with loaded scripts attr_reader :scripts def initialize(config) @config = config @logger = config.logger @scripts = Scripts.fetch(config.scripts_path) end # # Execute a lua script with the provided script_name # # @note this method is recursive if we need to load a lua script # that wasn't previously loaded. # # @param [Symbol] script_name the name of the script to execute # @param [Redis] conn the redis connection to use for execution # @param [Array] keys script keys # @param [Array] argv script arguments # # @return value from script # def execute(script_name, conn, keys: [], argv: []) result, elapsed = timed do scripts.execute(script_name, conn, keys: keys, argv: argv) end logger.debug("Executed #{script_name}.lua in #{elapsed}ms") result rescue ::RedisClient::CommandError => ex handle_error(script_name, conn, ex) do execute(script_name, conn, keys: keys, argv: argv) end end private # # Handle errors to allow retrying errors that need retrying # # @param [RedisClient::CommandError] ex exception to handle # # @return [void] # # @yieldreturn [void] yields back to the caller when NOSCRIPT is raised def handle_error(script_name, conn, ex) case ex.message when /NOSCRIPT/ handle_noscript(script_name) { return yield } when /BUSY/ handle_busy(conn) { return yield } end raise unless LuaError.intercepts?(ex) script = scripts.fetch(script_name, conn) raise LuaError.new(ex, script) end def handle_noscript(script_name) scripts.delete(script_name) yield end def handle_busy(conn) scripts.kill(conn) rescue ::RedisClient::CommandError => ex logger.warn(ex) ensure yield end end end end