Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Redlock 2 #38

Merged
merged 2 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
# waiting for the release with the fix for https://github.com/thoughtbot/appraisal/issues/199 to test on Ruby 3.2
ruby: [ '2.6', '2.7', '3.0', '3.1' ]
ruby: [ '2.7', '3.0', '3.1', '3.2' ]
env:
RUBY_IMAGE: ${{ matrix.ruby }}
name: Ruby ${{ matrix.ruby }}
Expand All @@ -22,7 +21,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
ruby-version: '3.0'
- name: Install dependencies
run: |
gem install dip
Expand All @@ -33,3 +32,5 @@ jobs:
run: dip rspec agnostic
- name: Run Rails tests
run: dip rspec rails
- name: Run Redlock tests
run: dip rspec redlock.1
8 changes: 8 additions & 0 deletions Appraisals
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ end
appraise "rails.6" do
gem "rails", "~> 6"
end

appraise "rails.7" do
gem "rails", "~> 7"
end

appraise "redlock.1" do
gem "redlock", "~> 1.3"
end
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ When you deploy your schedule to production, you want to start new instance befo
You can configure Redis client as the following:

```ruby
Schked.config.redis_servers = ["redis://127.0.0.1:7777", "redis://127.0.0.1:7778", "redis://127.0.0.1:7779"]
Schked.config.redis = {url: ENV.fetch("REDIS_URL") }
```

### Callbacks
Expand Down
4 changes: 4 additions & 0 deletions dip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ interaction:
default_args: spec/lib
command: bundle exec appraisal agnostic bundle exec rspec

redlock.1:
default_args: spec/lib
command: bundle exec appraisal redlock.1 bundle exec rspec

rails:
default_args: spec/rails
command: bundle exec appraisal bundle exec rspec
Expand Down
7 changes: 7 additions & 0 deletions gemfiles/rails.7.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rails", "~> 7"

gemspec path: "../"
7 changes: 7 additions & 0 deletions gemfiles/redlock.1.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "redlock", "~> 1.3"

gemspec path: "../"
2 changes: 2 additions & 0 deletions lib/schked.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# frozen_string_literal: true

require "connection_pool"
require "redlock"

require "schked/version"
require "schked/config"
require "schked/worker"
require "schked/redis_locker"
require "schked/redis_client_factory"
require "schked/railtie" if defined?(Rails)

module Schked
Expand Down
21 changes: 18 additions & 3 deletions lib/schked/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Schked
class Config
attr_writer :logger,
:do_not_load_root_schedule,
:redis_servers,
:redis,
:standalone

def paths
Expand Down Expand Up @@ -48,8 +48,23 @@ def fire_around_callback(name, job, calls = callbacks[name], &block)
end
end

def redis_servers
@redis_servers ||= [ENV.fetch("REDIS_URL", "redis://127.0.0.1:6379")]
def redis
@redis ||= {url: ENV.fetch("REDIS_URL", "redis://127.0.0.1:6379")}
end

def redis_servers=(val)
val = val.first

if val.is_a?(String)
self.redis = {url: val}
elsif val.respond_to?(:_client)
conf = val._client.config
self.redis = {url: conf.server_url, username: conf.username, password: conf.password}
else
raise ArgumentError, "Schked `redis_servers=` config option is deprecated. Please use `redis=` with a Hash"
end

warn "🔥 Schked `redis_servers=` config option is deprecated. Please use `redis=` with a Hash. Called from #{caller(1..1).first}"
end

def standalone?
Expand Down
34 changes: 34 additions & 0 deletions lib/schked/redis_client_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

require "redis-client"

module Schked
module RedisClientFactory
def self.build(options)
unless options.key?(:reconnect_attempts)
options[:reconnect_attempts] = 3
end

if options.key?(:sentinels)
if (url = options.delete(:url))
uri = URI.parse(url)
if !options.key?(:name) && uri.host
options[:name] = uri.host
end

if !options.key?(:password) && uri.password && !uri.password.empty?
options[:password] = uri.password
end

if !options.key?(:username) && uri.user && !uri.user.empty?
options[:username] = uri.user
end
end

RedisClient.sentinel(**options).new_client
else
RedisClient.config(**options).new_client
end
end
end
end
12 changes: 10 additions & 2 deletions lib/schked/redis_locker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class RedisLocker
LOCK_KEY = "schked:redis_locker"
LOCK_TTL = 60_000 # ms

def initialize(redis_servers, lock_ttl: LOCK_TTL, logger: Logger.new($stdout))
@lock_manager = Redlock::Client.new(redis_servers, retry_count: 0)
def initialize(redis_conf, lock_ttl: LOCK_TTL, logger: Logger.new($stdout))
@lock_manager = Redlock::Client.new([redis_client(redis_conf)], retry_count: 0)
@lock_ttl = lock_ttl
@logger = logger
end
Expand Down Expand Up @@ -49,6 +49,14 @@ def valid_lock?

private

def redis_client(redis_conf)
if Gem::Version.new(Redlock::VERSION) >= Gem::Version.new("2.0.0")
ConnectionPool::Wrapper.new { RedisClientFactory.build(redis_conf) }
else
ConnectionPool::Wrapper.new { Redis.new(**redis_conf) }
end
end

def try_lock
@lock_id = lock_manager.lock(LOCK_KEY, lock_ttl)
end
Expand Down
3 changes: 2 additions & 1 deletion lib/schked/worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class Worker
def initialize(config:)
@config = config

@locker = RedisLocker.new(config.redis_servers, lock_ttl: 40_000, logger: config.logger) unless config.standalone?
@locker = RedisLocker.new(config.redis, lock_ttl: 40_000, logger: config.logger) unless config.standalone?

@scheduler = Rufus::Scheduler.new(trigger_lock: locker)

watch_signals
Expand Down
8 changes: 4 additions & 4 deletions schked.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ Gem::Specification.new do |s|
s.bindir = "exe"
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
s.require_paths = ["lib"]
s.required_ruby_version = "> 2.5"
s.required_ruby_version = ">= 2.7"

s.add_dependency "redlock"
s.add_dependency "connection_pool", "~> 2.0"
s.add_dependency "redlock", "> 1.0", "< 3.0"
s.add_dependency "rufus-scheduler", "~> 3.0"
s.add_dependency "thor"

s.add_development_dependency "appraisal", "~> 2.2"
s.add_development_dependency "bundler", ">= 1.16"
s.add_development_dependency "combustion", "~> 1.3"
s.add_development_dependency "pry-byebug", "~> 3.9"
s.add_development_dependency "rake", "~> 13.0"
s.add_development_dependency "redis", "~> 5.0"
s.add_development_dependency "redis-client", "~> 0.10"
s.add_development_dependency "rspec", "~> 3.9"
s.add_development_dependency "standard", "~> 0.4"
end
13 changes: 7 additions & 6 deletions spec/lib/schked/redis_locker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
require "spec_helper"

describe Schked::RedisLocker do
let(:redis_servers) { [ENV["REDIS_URL"]] }
subject { described_class.new(redis_servers) }
let(:redis_conf) { {url: ENV["REDIS_URL"]} }

subject { described_class.new(redis_conf) }

describe "#lock" do
it "locks" do
Expand All @@ -13,7 +14,7 @@

context "when is locked by someone else" do
before do
described_class.new(redis_servers).lock
described_class.new(redis_conf).lock
end

it "fails to lock" do
Expand Down Expand Up @@ -45,7 +46,7 @@

context "when is locked by someone else" do
before do
described_class.new(redis_servers).lock
described_class.new(redis_conf).lock
end

it "fails to unlock" do
Expand All @@ -55,7 +56,7 @@
end

describe "#extend_lock" do
subject { described_class.new(redis_servers, lock_ttl: 1000) }
subject { described_class.new(redis_conf, lock_ttl: 1000) }

context "when is locked by us" do
it "extends lock" do
Expand All @@ -71,7 +72,7 @@

context "when is locked by someone else" do
before do
described_class.new(redis_servers, lock_ttl: 1000).lock
described_class.new(redis_conf, lock_ttl: 1000).lock
end

it "fails to extend" do
Expand Down
1 change: 0 additions & 1 deletion spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
ENV["RAILS_ENV"] = "test"

require "bundler/setup"
require "pry-byebug"
require "combustion"

Combustion.initialize!
Expand Down
5 changes: 2 additions & 3 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
ENV["RACK_ENV"] = "test"

require "bundler/setup"
require "pry-byebug"
require "schked"
require "redis"

RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
Expand All @@ -17,7 +15,8 @@

config.filter_run_when_matching :focus

redis = RedisClient.new(url: ENV["REDIS_URL"])
config.before(:each) do
Redis.new(url: ENV["REDIS_URL"]).flushdb
redis.call("FLUSHDB")
end
end