-
-
Notifications
You must be signed in to change notification settings - Fork 276
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
765 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.