Skip to content

Commit

Permalink
fix: inline redis_script
Browse files Browse the repository at this point in the history
  • Loading branch information
mhenrixon committed Jan 24, 2024
1 parent 8f4467a commit c72904f
Show file tree
Hide file tree
Showing 19 changed files with 765 additions and 14 deletions.
3 changes: 1 addition & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ gem "faraday-retry"
gem "gem-release"
gem "github-markup"
gem "rack-test"
gem "rake", "13.0.3"
gem "reek", ">= 5.3"
gem "rake"
gem "rspec"
gem "rspec-benchmark"
gem "rspec-html-matchers"
Expand Down
8 changes: 5 additions & 3 deletions lib/sidekiq_unique_jobs.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
# frozen_string_literal: true

require "brpoplpush/redis_script"
require "concurrent/executor/ruby_single_thread_executor"
require "concurrent/future"
require "concurrent/promises"
require "concurrent/map"
require "concurrent/mutable_struct"
require "concurrent/promises"
require "concurrent/timer_task"
require "concurrent/executor/ruby_single_thread_executor"
require "digest"
require "digest/sha1"
require "erb"
require "forwardable"
require "json"
require "pathname"
require "redis_client"
require "sidekiq"

require "sidekiq_unique_jobs/redis_script"

require "sidekiq_unique_jobs/deprecation"
require "sidekiq_unique_jobs/reflections"
require "sidekiq_unique_jobs/reflectable"
Expand Down
2 changes: 1 addition & 1 deletion lib/sidekiq_unique_jobs/changelog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def page(cursor: 0, pattern: "*", page_size: 100)
# NOTE: When debugging, check the last item in the returned array.
[
total_size.to_i,
result[0].to_i, # next_cursor
result[0].to_i, # next_cursor
result[1].map { |entry| load_json(entry) }.select { |entry| entry.is_a?(Hash) },
]
end
Expand Down
58 changes: 58 additions & 0 deletions lib/sidekiq_unique_jobs/redis_script.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

require "sidekiq_unique_jobs/redis_script/template"
require "sidekiq_unique_jobs/redis_script/lua_error"
require "sidekiq_unique_jobs/redis_script/script"
require "sidekiq_unique_jobs/redis_script/scripts"
require "sidekiq_unique_jobs/redis_script/config"
require "sidekiq_unique_jobs/redis_script/timing"
require "sidekiq_unique_jobs/redis_script/logging"
require "sidekiq_unique_jobs/redis_script/dsl"
require "sidekiq_unique_jobs/redis_script/client"

module SidekiqUniqueJobs
# Interface to dealing with .lua files
#
# @author Mikael Henriksson <[email protected]>
module RedisScript
module_function

include SidekiqUniqueJobs::RedisScript::DSL

#
# The current logger
#
#
# @return [Logger] the configured logger
#
def logger
config.logger
end

#
# Set a new logger
#
# @param [Logger] other another logger
#
# @return [Logger] the new logger
#
def logger=(other)
config.logger = other
end

#
# Execute the given script_name
#
#
# @param [Symbol] script_name the name of the lua script
# @param [Array<String>] keys script keys
# @param [Array<Object>] argv script arguments
# @param [Redis] conn the redis connection to use
#
# @return value from script
#
def execute(script_name, conn, keys: [], argv: [])
Client.execute(script_name, conn, keys: keys, argv: argv)
end
end
end
94 changes: 94 additions & 0 deletions lib/sidekiq_unique_jobs/redis_script/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# frozen_string_literal: true

module SidekiqUniqueJobs
module RedisScript
# Interface to dealing with .lua files
#
# @author Mikael Henriksson <[email protected]>
class Client
include SidekiqUniqueJobs::RedisScript::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<String>] keys script keys
# @param [Array<Object>] 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 ::Redis::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 [Redis::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 ::Redis::CommandError => ex
logger.warn(ex)
ensure
yield
end
end
end
end
68 changes: 68 additions & 0 deletions lib/sidekiq_unique_jobs/redis_script/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# frozen_string_literal: true

module SidekiqUniqueJobs
module RedisScript
#
# Class holding gem configuration
#
# @author Mikael Henriksson <[email protected]>
class Config
#
# @!attribute [r] logger
# @return [Logger] a logger to use for debugging
attr_reader :logger
#
# @!attribute [r] scripts_path
# @return [Pathname] a directory with lua scripts
attr_reader :scripts_path

#
# Initialize a new instance of {Config}
#
#
def initialize
@conn = RedisClient.new
@logger = Logger.new($stdout)
@scripts_path = nil
end

#
# Sets a value for scripts_path
#
# @param [String, Pathname] obj <description>
#
# @raise [ArgumentError] when directory does not exist
# @raise [ArgumentError] when argument isn't supported
#
# @return [Pathname]
#
def scripts_path=(obj)
raise ArgumentError, "#{obj} should be a Pathname or String" unless obj.is_a?(Pathname) || obj.is_a?(String)
raise ArgumentError, "#{obj} does not exist" unless Dir.exist?(obj.to_s)

@scripts_path =
case obj
when String
Pathname.new(obj)
else
obj
end
end

#
# Sets a value for logger
#
# @param [Logger] obj a logger to use
#
# @raise [ArgumentError] when given argument isn't a Logger
#
# @return [Logger]
#
def logger=(obj)
raise ArgumentError, "#{obj} should be a Logger" unless obj.is_a?(Logger)

@logger = obj
end
end
end
end
60 changes: 60 additions & 0 deletions lib/sidekiq_unique_jobs/redis_script/dsl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module SidekiqUniqueJobs
module RedisScript
# Interface to dealing with .lua files
#
# @author Mikael Henriksson <[email protected]>
module DSL
MUTEX = Mutex.new

def self.included(base)
base.class_eval do
extend ClassMethods
end
end

#
# Module ClassMethods extends the base class with necessary methods
#
# @author Mikael Henriksson <[email protected]>
#
module ClassMethods
def execute(file_name, conn, keys: [], argv: [])
SidekiqUniqueJobs::RedisScript::Client
.new(config)
.execute(file_name, conn, keys: keys, argv: argv)
end

# Configure the gem
#
# This is usually called once at startup of an application
# @param [Hash] options global gem options
# @option options [String, Pathname] :path
# @option options [Logger] :logger (default is Logger.new(STDOUT))
# @yield control to the caller when given block
def configure(options = {})
if block_given?
yield config
else
options.each do |key, val|
config.send(:"#{key}=", val)
end
end
end

#
# The current configuration (See: {.configure} on how to configure)
#
#
# @return [RedisScript::Config] the gem configuration
#
def config
MUTEX.synchronize do
@config ||= Config.new
end
end
end
end
end
end
Loading

0 comments on commit c72904f

Please sign in to comment.