rails_failover 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 696636b3992c9893a48a23366f760af354c496bbd1573754bdd92b66fc9913ff
4
- data.tar.gz: 8994846710a6765afffc3a60845bc696efb53e44d532a04092d3a6ba87adf4d9
3
+ metadata.gz: 1246e6e2f88b79171c60042aef00f64877c7292c316bd8aa7cb218a7c663c817
4
+ data.tar.gz: 1ca0033ff058c31d97b29635d7cdcbde05cf4e09a544cde00ca9f6c3b7e0322a
5
5
  SHA512:
6
- metadata.gz: fe74527339c42947a6205a231f6035af8e039809db86df7d64edb34d02d3cf59fa4f814b05e28f077dd89a61448b5fcde1d70094a5f6c90c81d31cb73d4bd059
7
- data.tar.gz: f3a8df28c08ddd305833f1c3ad630c8c3f270bb398aa6698e39b74a413ba03c03614986e3da944e061547d8d70c764b92c90767b2e1bec045a1661cd941563a5
6
+ metadata.gz: b51f968031ce4b1313f8ae9a299b3e179689bad66121094085955a2fc9d75d24d4a2fcfafbbd6b6783e363a1329ab6747ddd6d9749bf98b327b304940bb13c73
7
+ data.tar.gz: 77037a16dc321fadb5866cf11a04e220702c1f8e911986da711943c5565b97589d71f21bbf3fb8ca6c8e41d4cd89e090a44fa391b61cc0c10441fe4ba631982f
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: Setup ruby
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
- ruby-version: '3.2'
19
+ ruby-version: "3.2"
20
20
  bundler-cache: true
21
21
 
22
22
  - name: Rubocop
@@ -29,13 +29,14 @@ jobs:
29
29
  bundle exec stree check Gemfile rails_failover.gemspec $(git ls-files '*.rb')
30
30
 
31
31
  redis:
32
- name: 'Redis (Ruby ${{ matrix.ruby }})'
32
+ name: "Redis (Redis gem ~> ${{ matrix.redis_gem }}, Ruby ${{ matrix.ruby }})"
33
33
  runs-on: ubuntu-latest
34
34
 
35
35
  strategy:
36
36
  fail-fast: false
37
37
  matrix:
38
- ruby: ['3.4', '3.3', '3.2', '3.1']
38
+ ruby: ["3.4", "3.3", "3.2", "3.1"]
39
+ redis_gem: ["4.8", "5.3"]
39
40
 
40
41
  steps:
41
42
  - uses: actions/checkout@v3
@@ -44,31 +45,37 @@ jobs:
44
45
  uses: ruby/setup-ruby@v1
45
46
  with:
46
47
  ruby-version: ${{ matrix.ruby }}
47
- bundler-cache: true
48
+
49
+ - name: Setup gems
50
+ env:
51
+ REDIS_GEM_VERSION: ${{ matrix.redis_gem }}
52
+ run: bundle install
48
53
 
49
54
  - name: Setup redis
50
55
  run: sudo apt-get install redis-server
51
56
 
52
57
  - name: Redis specs
58
+ env:
59
+ REDIS_GEM_VERSION: ${{ matrix.redis_gem }}
53
60
  run: bin/rspec redis
54
61
 
55
62
  active_record:
56
63
  runs-on: ubuntu-latest
57
- name: 'ActiveRecord ~>${{ matrix.rails }} (Ruby ${{ matrix.ruby }})'
64
+ name: "ActiveRecord ~>${{ matrix.rails }} (Ruby ${{ matrix.ruby }})"
58
65
 
59
66
  strategy:
60
67
  fail-fast: false
61
68
  matrix:
62
- ruby: ['3.4', '3.3', '3.2', '3.1']
63
- rails: ['7.1.0', '7.0.0']
69
+ ruby: ["3.4", "3.3", "3.2", "3.1"]
70
+ rails: ["7.1.0", "7.0.0"]
64
71
  include:
65
- - ruby: '3.2'
66
- rails: '6.1.0'
72
+ - ruby: "3.2"
73
+ rails: "6.1.0"
67
74
  exclude:
68
- - ruby: '3.4'
69
- rails: '7.0.0'
70
- - ruby: '3.3'
71
- rails: '7.0.0'
75
+ - ruby: "3.4"
76
+ rails: "7.0.0"
77
+ - ruby: "3.3"
78
+ rails: "7.0.0"
72
79
 
73
80
  steps:
74
81
  - uses: actions/checkout@v3
@@ -84,13 +91,20 @@ jobs:
84
91
  - name: Setup postgres
85
92
  run: |
86
93
  make setup_pg
87
- make start_pg
88
94
 
89
95
  - name: ActiveRecord specs
90
96
  env:
91
97
  RAILS_VERSION: ${{ matrix.rails }}
92
98
  run: bin/rspec active_record
93
99
 
100
+ - name: Dump Unicorn STDERR logs
101
+ if: ${{ failure() }}
102
+ run: cat spec/support/dummy_app/log/unicorn.stderr.log
103
+
104
+ - name: Dump Rails logs
105
+ if: ${{ failure() }}
106
+ run: cat spec/support/dummy_app/log/production.log
107
+
94
108
  publish:
95
109
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
96
110
  needs: [lint, redis, active_record]
data/.gitignore CHANGED
@@ -16,4 +16,5 @@
16
16
  *.rdb
17
17
 
18
18
  *.gem
19
- Gemfile.lock
19
+ **/Gemfile.lock
20
+ /Gemfile.lock
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --color
2
2
  --require spec_helper
3
3
  --format documentation
4
+ --order random
data/CHANGELOG.md CHANGED
@@ -5,7 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.2.0] - 2025-01-29
9
+
10
+ - DEV: Add compatibility with the Redis gem version 5.x
11
+
12
+ ## [2.1.1] - 2024-07-02
13
+
14
+ - FIX: Falling back to primary PG server not reliable on Rails 7.1
15
+
8
16
  ## [2.1.0] - 2024-05-29
17
+
9
18
  - DEV: Update dependencies to officially support Rails 7.1
10
19
 
11
20
  ## [2.0.1] - 2023-05-30
data/Gemfile CHANGED
@@ -3,3 +3,5 @@ source "https://rubygems.org"
3
3
 
4
4
  # Specify your gem's dependencies in rails_failover.gemspec
5
5
  gemspec
6
+
7
+ gem "redis", "~> #{ENV["REDIS_GEM_VERSION"] || "5.3"}"
data/README.md CHANGED
@@ -79,7 +79,25 @@ connects_to database: { writing: :primary, second_database_writing: :second_data
79
79
  Add `require 'rails_failover/redis'` before creating a `Redis` instance.
80
80
 
81
81
  ```ruby
82
- Redis.new(host: "127.0.0.1", port: 6379, replica_host: "127.0.0.1", replica_port: 6380, connector: RailsFailover::Redis::Connector)
82
+ # Redis/RedisClient 4.x
83
+ Redis.new(
84
+ host: "127.0.0.1",
85
+ port: 6379,
86
+ replica_host: "127.0.0.1",
87
+ replica_port: 6380,
88
+ connector: RailsFailover::Redis::Connector,
89
+ )
90
+
91
+ # Redis/RedisClient 5.x
92
+ Redis.new(
93
+ host: "127.0.0.1",
94
+ port: 6379,
95
+ client_implementation: RailsFailover::Redis::Client,
96
+ custom: {
97
+ replica_host: "127.0.0.1",
98
+ replica_port: 6380,
99
+ }
100
+ )
83
101
  ```
84
102
 
85
103
  Callbacks can be registered when the primary connection is down and when it is up.
@@ -94,6 +112,8 @@ RailsFailover::Redis.on_fallback_callback do
94
112
  end
95
113
  ```
96
114
 
115
+ > ⚠️ If you’re using Sidekiq, don’t provide it with the replica configuration as it won’t work. RailsFailover works with a replica in read-only mode, meaning Sidekiq wouldn’t work properly anyway as it needs to write to Redis.
116
+
97
117
  ## Development
98
118
 
99
119
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -107,7 +127,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
107
127
  The ActiveRecord failover tests are run against a dummy Rails server. Run the following commands to run the test:
108
128
 
109
129
  1. `make setup_pg`
110
- 2. `make start_pg`
111
130
  3. `bin/rspec active_record`. You may also run the tests with more unicorn workers by adding the `UNICORN_WORKERS` env variable.
112
131
 
113
132
  #### Redis
@@ -60,10 +60,11 @@ module RailsFailover
60
60
 
61
61
  begin
62
62
  connection =
63
- ::ActiveRecord::Base.connection_handler.retrieve_connection(
64
- spec_name,
65
- role: handler_key,
66
- )
63
+ ::ActiveRecord::Base
64
+ .connection_handler
65
+ .retrieve_connection(spec_name, role: handler_key)
66
+ .tap(&:verify!)
67
+
67
68
  connection_active = connection.active?
68
69
  rescue => e
69
70
  logger.debug "#{Process.pid} Connection to server for '#{handler_key} #{spec_name}' failed with '#{e.message}'"
@@ -11,7 +11,7 @@ module RailsFailover
11
11
  app.config.active_record_rails_failover = true
12
12
  ::ActiveSupport.on_load(:active_record) do
13
13
  begin
14
- ::ActiveRecord::Base.connection
14
+ ::ActiveRecord::Base.connection.verify!
15
15
  rescue ::ActiveRecord::NoDatabaseError
16
16
  # Do nothing since database hasn't been created
17
17
  rescue ::PG::Error, ::ActiveRecord::ConnectionNotEstablished
@@ -9,6 +9,7 @@ module RailsFailover
9
9
  original_driver = options[:driver]
10
10
  options[:primary_host] = options[:host]
11
11
  options[:primary_port] = options[:port]
12
+ options[:id] ||= "#{options[:host]}:#{options[:port]}"
12
13
 
13
14
  options[:driver] = Class.new(options[:driver]) do
14
15
  def self.connect(options)
@@ -40,25 +41,24 @@ module RailsFailover
40
41
 
41
42
  options[:original_driver] = original_driver
42
43
  options.delete(:connector)
43
- options[:id] ||= "#{options[:host]}:#{options[:port]}"
44
44
  @replica_options = replica_options(options)
45
45
  @options = options.dup
46
46
  end
47
47
 
48
48
  def resolve
49
- Handler.instance.primary_down?(@options) ? @replica_options : @options
49
+ Handler.instance.primary_down?(@options[:id]) ? @replica_options : @options
50
50
  end
51
51
 
52
52
  def check(client)
53
- Handler.instance.register_client(client)
54
- expected_role = Handler.instance.primary_down?(@options) ? REPLICA : PRIMARY
53
+ Handler.instance.register_client(client, client.options[:id])
54
+ expected_role = Handler.instance.primary_down?(@options[:id]) ? REPLICA : PRIMARY
55
55
  if client.connection.rails_failover_role != expected_role
56
56
  raise ::Redis::CannotConnectError, "Opened with unexpected failover role"
57
57
  end
58
58
  end
59
59
 
60
60
  def on_disconnect(client)
61
- Handler.instance.deregister_client(client)
61
+ Handler.instance.deregister_client(client, client.options[:id])
62
62
  end
63
63
 
64
64
  private
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../handler_base"
4
+
5
+ module RailsFailover
6
+ class Redis
7
+ class Handler < HandlerBase
8
+ def new_primary_client(options)
9
+ options[:driver] = options[:original_driver]
10
+ ::Redis::Client.new(options)
11
+ end
12
+
13
+ def primary_client_info(client)
14
+ client.call([:info])
15
+ end
16
+
17
+ def soft_disconnect_original_client(matched_clients, redis, role)
18
+ # When subscribed, Redis#_client is not a Redis::Client
19
+ # Instance variable is the only reliable way
20
+ client = redis.instance_variable_get(:@original_client)
21
+ return if !matched_clients.include?(client)
22
+ soft_disconnect(redis, client, role)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "redis"
4
+ require "active_support/core_ext/module/delegation"
4
5
 
5
6
  # See https://github.com/redis/redis-rb/pull/908
6
7
  class Redis::Client
8
+ delegate :rails_failover_role, :shutdown_socket, to: :connection, allow_nil: true
9
+
7
10
  def disconnect
8
11
  if connected?
9
12
  result = connection.disconnect
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsFailover
4
+ class Redis
5
+ class Client < ::Redis::Client
6
+ def initialize(config, **kwargs)
7
+ super
8
+ @config = RailsFailover::Redis::Config.new(config)
9
+ end
10
+
11
+ def connect
12
+ Handler.instance.register_client(self, id)
13
+ super
14
+ end
15
+
16
+ def on_disconnect
17
+ Handler.instance.deregister_client(self, id)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require_relative "handler"
5
+
6
+ module RailsFailover
7
+ class Redis
8
+ class Config < SimpleDelegator
9
+ attr_reader :driver, :primary_host, :primary_port, :id
10
+ attr_accessor :rails_failover_role
11
+
12
+ def initialize(object)
13
+ super
14
+ @primary_host = object.host
15
+ @primary_port = object.port
16
+ @id ||= "#{object.host}:#{object.port}"
17
+ @driver =
18
+ Class.new(object.driver) do
19
+ def connect
20
+ is_primary =
21
+ (config.host == config.primary_host) && (config.port == config.primary_port)
22
+ super.tap { config.rails_failover_role = is_primary ? PRIMARY : REPLICA }
23
+ rescue ::Redis::TimeoutError,
24
+ RedisClient::CannotConnectError,
25
+ SocketError,
26
+ Errno::EADDRNOTAVAIL,
27
+ Errno::ECONNREFUSED,
28
+ Errno::EHOSTDOWN,
29
+ Errno::EHOSTUNREACH,
30
+ Errno::ENETUNREACH,
31
+ Errno::ENOENT,
32
+ Errno::ETIMEDOUT,
33
+ Errno::EINVAL => e
34
+ Handler.instance.verify_primary(config) if is_primary
35
+ raise e
36
+ end
37
+ end
38
+ end
39
+
40
+ def host
41
+ return super unless Handler.instance.primary_down?(id)
42
+ custom[:replica_host]
43
+ end
44
+
45
+ def port
46
+ return super unless Handler.instance.primary_down?(id)
47
+ custom[:replica_port]
48
+ end
49
+
50
+ def new_primary_client
51
+ ::Redis::Client.new(__getobj__)
52
+ end
53
+
54
+ def [](key)
55
+ instance_variable_get("@#{key}")
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../handler_base"
4
+
5
+ module RailsFailover
6
+ class Redis
7
+ class Handler < HandlerBase
8
+ def new_primary_client(config)
9
+ config.new_primary_client
10
+ end
11
+
12
+ def primary_client_info(client)
13
+ client.call_v([:info])
14
+ end
15
+
16
+ def soft_disconnect_original_client(matched_clients, redis, role)
17
+ return if !matched_clients.include?(redis._client)
18
+ soft_disconnect(redis, redis._client, role)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis"
4
+ require "active_support/core_ext/module/delegation"
5
+
6
+ class Redis::Client
7
+ delegate :rails_failover_role, to: :config, allow_nil: true
8
+
9
+ alias shutdown_socket close
10
+
11
+ def disconnect
12
+ close
13
+ on_disconnect
14
+ self
15
+ end
16
+
17
+ def on_disconnect
18
+ end
19
+ end
20
+
21
+ class Redis::SubscribedClient
22
+ delegate :connected?, to: :@client
23
+ end
24
+
25
+ class RedisClient::PubSub
26
+ delegate :connected?, to: :@raw_connection, allow_nil: true
27
+ end
@@ -6,7 +6,7 @@ require "concurrent"
6
6
 
7
7
  module RailsFailover
8
8
  class Redis
9
- class Handler
9
+ class HandlerBase
10
10
  include Singleton
11
11
  include MonitorMixin
12
12
 
@@ -23,8 +23,8 @@ module RailsFailover
23
23
  super() # Monitor#initialize
24
24
  end
25
25
 
26
- def verify_primary(options)
27
- primary_down(options)
26
+ def verify_primary(config)
27
+ primary_down(config)
28
28
 
29
29
  mon_synchronize do
30
30
  return if @thread&.alive?
@@ -33,18 +33,16 @@ module RailsFailover
33
33
  end
34
34
  end
35
35
 
36
- def register_client(client)
37
- id = client.options[:id]
36
+ def register_client(client, id)
38
37
  clients_for_id(id).put_if_absent(client, true)
39
38
  end
40
39
 
41
- def deregister_client(client)
42
- id = client.options[:id]
40
+ def deregister_client(client, id)
43
41
  clients_for_id(id).delete(client)
44
42
  end
45
43
 
46
- def primary_down?(options)
47
- primaries_down[options[:id]]
44
+ def primary_down?(id)
45
+ primaries_down[id]
48
46
  end
49
47
 
50
48
  def primaries_down_count
@@ -71,15 +69,14 @@ module RailsFailover
71
69
 
72
70
  active_primaries_keys = {}
73
71
 
74
- primaries_down.each do |key, options|
72
+ primaries_down.each do |key, config|
75
73
  info = nil
76
- options = options.dup
74
+ config = config.dup
77
75
 
78
76
  begin
79
- options[:driver] = options[:original_driver]
80
- primary_client = ::Redis::Client.new(options)
77
+ primary_client = new_primary_client(config)
81
78
  logger&.debug "Checking connection to primary server (#{key})"
82
- info = primary_client.call([:info])
79
+ info = primary_client_info(primary_client)
83
80
  rescue => e
84
81
  logger&.debug "Connection to primary server (#{key}) failed with '#{e.message}'"
85
82
  ensure
@@ -87,14 +84,14 @@ module RailsFailover
87
84
  end
88
85
 
89
86
  if info && info.include?(PRIMARY_LOADED_STATUS) && info.include?(PRIMARY_ROLE_STATUS)
90
- active_primaries_keys[key] = options
87
+ active_primaries_keys[key] = config
91
88
  logger&.debug "Primary server (#{key}) is active, disconnecting clients from replica"
92
89
  end
93
90
  end
94
91
 
95
- active_primaries_keys.each do |key, options|
96
- primary_up(options)
97
- disconnect_clients(options, RailsFailover::Redis::REPLICA)
92
+ active_primaries_keys.each do |key, config|
93
+ primary_up(config)
94
+ disconnect_clients(config[:id], RailsFailover::Redis::REPLICA)
98
95
  end
99
96
  end
100
97
 
@@ -102,14 +99,14 @@ module RailsFailover
102
99
  primaries_down.empty?
103
100
  end
104
101
 
105
- def primary_up(options)
106
- already_up = !primaries_down.delete(options[:id])
107
- RailsFailover::Redis.on_fallback_callback!(options[:id]) if !already_up
102
+ def primary_up(config)
103
+ already_up = !primaries_down.delete(config[:id])
104
+ RailsFailover::Redis.on_fallback_callback!(config[:id]) if !already_up
108
105
  end
109
106
 
110
- def primary_down(options)
111
- already_down = primaries_down.put_if_absent(options[:id], options.dup)
112
- RailsFailover::Redis.on_failover_callback!(options[:id]) if !already_down
107
+ def primary_down(config)
108
+ already_down = primaries_down.put_if_absent(config[:id], config.dup)
109
+ RailsFailover::Redis.on_failover_callback!(config[:id]) if !already_down
113
110
  end
114
111
 
115
112
  def primaries_down
@@ -121,7 +118,7 @@ module RailsFailover
121
118
  end
122
119
 
123
120
  ancestor_pids&.each do |pid|
124
- @primaries_down.delete(pid)&.each { |id, options| verify_primary(options) }
121
+ @primaries_down.delete(pid)&.each { |id, config| verify_primary(config) }
125
122
  end
126
123
 
127
124
  value
@@ -143,27 +140,21 @@ module RailsFailover
143
140
  end
144
141
 
145
142
  def ensure_primary_clients_disconnected
146
- primaries_down.each do |key, options|
147
- disconnect_clients(options, RailsFailover::Redis::PRIMARY)
143
+ primaries_down.each do |key, config|
144
+ disconnect_clients(config[:id], RailsFailover::Redis::PRIMARY)
148
145
  end
149
146
  end
150
147
 
151
- def disconnect_clients(options, role)
152
- id = options[:id]
153
-
148
+ def disconnect_clients(id, role)
154
149
  matched_clients =
155
- clients_for_id(id)&.keys&.filter { |c| c.connection.rails_failover_role == role }&.to_set
150
+ clients_for_id(id)&.keys&.select { _1.rails_failover_role == role }&.to_set
156
151
 
157
152
  return if matched_clients.nil? || matched_clients.empty?
158
153
 
159
154
  # This is not ideal, but the mutex we need is contained
160
155
  # in the ::Redis instance, not the Redis::Client
161
156
  ObjectSpace.each_object(::Redis) do |redis|
162
- # When subscribed, Redis#_client is not a Redis::Client
163
- # Instance variable is the only reliable way
164
- client = redis.instance_variable_get(:@original_client)
165
- next if !matched_clients.include?(client)
166
- soft_disconnect(redis, client, role)
157
+ soft_disconnect_original_client(matched_clients, redis, role)
167
158
  end
168
159
  end
169
160
 
@@ -174,7 +165,7 @@ module RailsFailover
174
165
 
175
166
  if !has_lock
176
167
  begin
177
- client.connection.shutdown_socket
168
+ client.shutdown_socket
178
169
  rescue => e
179
170
  logger&.warn "Redis shutdown_socket for (#{role}) failed with #{e.class} '#{e.message}'"
180
171
  end
@@ -182,15 +173,14 @@ module RailsFailover
182
173
  waiting_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
183
174
  loop do # Keep trying
184
175
  break if has_lock = redis_mon_try_enter(redis)
185
- break if !client.connection.connected? # Disconnected by other thread
186
- break if client.connection.rails_failover_role != role # Reconnected by other thread
176
+ break if !client.connected? # Disconnected by other thread
177
+ break if client.rails_failover_role != role # Reconnected by other thread
187
178
  time_now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
188
179
  break if time_now > waiting_since + SOFT_DISCONNECT_TIMEOUT_SECONDS
189
180
  sleep SOFT_DISCONNECT_POLL_SECONDS
190
181
  end
191
182
  end
192
-
193
- client.disconnect if client.connection&.rails_failover_role == role
183
+ client.disconnect if client.rails_failover_role == role
194
184
  ensure
195
185
  redis_mon_exit(redis) if has_lock
196
186
  end
@@ -8,8 +8,14 @@ if Gem::Version.new(Redis::VERSION) < Gem::Version.new(supported_version)
8
8
  raise "redis #{Redis::VERSION} is not supported. Please upgrade to Redis #{supported_version}."
9
9
  end
10
10
 
11
- require_relative "../redis/patches/client"
12
- require_relative "redis/connector"
11
+ if Redis::VERSION >= "5"
12
+ require_relative "redis/compat_5x/patches/client"
13
+ require_relative "redis/compat_5x/config"
14
+ require_relative "redis/compat_5x/client"
15
+ else
16
+ require_relative "redis/compat_4x/patches/client"
17
+ require_relative "redis/compat_4x/connector"
18
+ end
13
19
 
14
20
  module RailsFailover
15
21
  class Redis
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsFailover
4
- VERSION = "2.1.0"
4
+ VERSION = "2.2.0"
5
5
  end
data/makefile CHANGED
@@ -3,19 +3,8 @@ include redis.mk
3
3
 
4
4
  all: redis
5
5
 
6
- active_record: teardown_dummy_rails_server setup_dummy_rails_server test_active_record
6
+ active_record: start_pg_primary create_test_database stop_pg test_active_record
7
7
 
8
8
  test_active_record:
9
+ @BUNDLE_GEMFILE=./spec/support/dummy_app/Gemfile bundle install --quiet
9
10
  @ACTIVE_RECORD=1 bundle exec rspec --tag type:active_record ${RSPEC_PATH}
10
-
11
- setup_dummy_rails_server:
12
- @cd spec/support/dummy_app && BUNDLE_GEMFILE=Gemfile bundle install --quiet && BUNDLE_GEMFILE=Gemfile RAILS_ENV=production $(BUNDLER_BIN) exec rails db:create db:migrate db:seed
13
-
14
- start_dummy_rails_server:
15
- @cd spec/support/dummy_app && BUNDLE_GEMFILE=Gemfile SECRET_KEY_BASE=somekey bundle exec unicorn -c config/unicorn.conf.rb -D -E production
16
-
17
- stop_dummy_rails_server:
18
- @kill -TERM $(shell cat spec/support/dummy_app/tmp/pids/unicorn.pid)
19
-
20
- teardown_dummy_rails_server:
21
- @cd spec/support/dummy_app && (! (bundle check > /dev/null 2>&1) || BUNDLE_GEMFILE=Gemfile DISABLE_DATABASE_ENVIRONMENT_CHECK=1 RAILS_ENV=production $(BUNDLER_BIN) exec rails db:drop)
data/postgresql.mk CHANGED
@@ -1,9 +1,9 @@
1
1
  PG_BIN_DIR := $(shell pg_config --bindir)
2
2
  PWD := $(shell pwd)
3
- PG_PRIMARY_DIR := $(PWD)/tmp/primary
3
+ PG_PRIMARY_DIR := /tmp/primary
4
4
  PG_PRIMARY_DATA_DIR := $(PG_PRIMARY_DIR)/data
5
5
  PG_PRIMARY_RUN_DIR := $(PG_PRIMARY_DIR)/run
6
- PG_REPLICA_DIR := $(PWD)/tmp/replica
6
+ PG_REPLICA_DIR := /tmp/replica
7
7
  PG_REPLICA_DATA_DIR := $(PG_REPLICA_DIR)/data
8
8
  PG_REPLICA_RUN_DIR := $(PG_REPLICA_DIR)/run
9
9
  PG_PRIMARY_PORT := 5434
@@ -14,11 +14,14 @@ PG_REPLICATION_SLOT_NAME := replication
14
14
 
15
15
  setup_pg: init_primary start_pg_primary setup_primary init_replica stop_pg_primary
16
16
 
17
+ create_test_database:
18
+ @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres --quiet -c "DROP DATABASE IF EXISTS test;"
19
+ @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres --quiet -c "CREATE DATABASE test;"
20
+
17
21
  setup_primary:
18
22
  @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "CREATE USER $(PG_REPLICATION_USER) WITH REPLICATION ENCRYPTED PASSWORD '$(PG_REPLICATION_PASSWORD)';"
19
23
  @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "SELECT * FROM pg_create_physical_replication_slot('$(PG_REPLICATION_SLOT_NAME)');"
20
24
  @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "CREATE USER test;"
21
- @$(PG_BIN_DIR)/psql -p $(PG_PRIMARY_PORT) -h $(PG_PRIMARY_RUN_DIR) -d postgres -c "CREATE DATABASE test;"
22
25
 
23
26
  start_pg: start_pg_primary start_pg_replica
24
27
 
@@ -36,19 +39,36 @@ init_replica:
36
39
  @chmod 0700 $(PG_REPLICA_DATA_DIR)
37
40
 
38
41
  start_pg_primary:
39
- @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" start
42
+ @if [ ! -d "$(PG_PRIMARY_DATA_DIR)" ] || ! $(PG_BIN_DIR)/pg_ctl status -D $(PG_PRIMARY_DATA_DIR) > /dev/null 2>&1; then \
43
+ $(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" start; \
44
+ while ! $(PG_BIN_DIR)/pg_ctl status -D $(PG_PRIMARY_DATA_DIR) > /dev/null 2>&1; do \
45
+ sleep 1; \
46
+ done; \
47
+ fi
40
48
 
41
49
  start_pg_replica:
42
- @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_REPLICA_DATA_DIR) -o "-p $(PG_REPLICA_PORT)" -o "-k $(PG_REPLICA_RUN_DIR)" start
43
-
44
- restart_pg_primary:
45
- @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" restart
50
+ @if [ ! -d "$(PG_REPLICA_DATA_DIR)" ] || ! $(PG_BIN_DIR)/pg_ctl status -D $(PG_REPLICA_DATA_DIR) > /dev/null 2>&1; then \
51
+ $(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_REPLICA_DATA_DIR) -o "-p $(PG_REPLICA_PORT)" -o "-k $(PG_REPLICA_RUN_DIR)" start; \
52
+ while ! $(PG_BIN_DIR)/pg_ctl status -D $(PG_REPLICA_DATA_DIR) > /dev/null 2>&1; do \
53
+ sleep 1; \
54
+ done; \
55
+ fi
46
56
 
47
57
  stop_pg_primary:
48
- @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" stop
58
+ @if [ -d "$(PG_PRIMARY_DATA_DIR)" ] && $(PG_BIN_DIR)/pg_ctl status -D $(PG_PRIMARY_DATA_DIR) > /dev/null 2>&1; then \
59
+ $(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_PRIMARY_DATA_DIR) -o "-p $(PG_PRIMARY_PORT)" -o "-k $(PG_PRIMARY_RUN_DIR)" stop; \
60
+ while $(PG_BIN_DIR)/pg_ctl status -D $(PG_PRIMARY_DATA_DIR) > /dev/null 2>&1; do \
61
+ sleep 1; \
62
+ done; \
63
+ fi
49
64
 
50
65
  stop_pg_replica:
51
- @$(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_REPLICA_DATA_DIR) -o "-p $(PG_REPLICA_PORT)" -o "-k $(PG_REPLICA_RUN_DIR)" stop
66
+ @if [ -d "$(PG_REPLICA_DATA_DIR)" ] && $(PG_BIN_DIR)/pg_ctl status -D $(PG_REPLICA_DATA_DIR) > /dev/null 2>&1; then \
67
+ $(PG_BIN_DIR)/pg_ctl --silent --log /dev/null -w -D $(PG_REPLICA_DATA_DIR) -o "-p $(PG_REPLICA_PORT)" -o "-k $(PG_REPLICA_RUN_DIR)" stop; \
68
+ while $(PG_BIN_DIR)/pg_ctl status -D $(PG_REPLICA_DATA_DIR) > /dev/null 2>&1; do \
69
+ sleep 1; \
70
+ done; \
71
+ fi
52
72
 
53
73
  cleanup_pg:
54
74
  @rm -rf $(PG_PRIMARY_DIR) $(PG_REPLICA_DIR)
@@ -27,11 +27,11 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency "railties", ">= 6.1", "< 8.0"
28
28
  spec.add_dependency "concurrent-ruby"
29
29
 
30
- spec.add_development_dependency "rake", "~> 12.0"
31
- spec.add_development_dependency "redis", "~> 4.1"
32
- spec.add_development_dependency "pg", "~> 1.2"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "redis", ">= 4.1", "< 6.0"
32
+ spec.add_development_dependency "pg"
33
33
  spec.add_development_dependency "rack"
34
- spec.add_development_dependency "rspec", "~> 3.0"
34
+ spec.add_development_dependency "rspec"
35
35
  spec.add_development_dependency "byebug"
36
36
  spec.add_development_dependency "rubocop-discourse"
37
37
  spec.add_development_dependency "syntax_tree"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_failover
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Tan
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-05-29 00:00:00.000000000 Z
10
+ date: 2025-01-29 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -68,44 +67,50 @@ dependencies:
68
67
  name: rake
69
68
  requirement: !ruby/object:Gem::Requirement
70
69
  requirements:
71
- - - "~>"
70
+ - - ">="
72
71
  - !ruby/object:Gem::Version
73
- version: '12.0'
72
+ version: '0'
74
73
  type: :development
75
74
  prerelease: false
76
75
  version_requirements: !ruby/object:Gem::Requirement
77
76
  requirements:
78
- - - "~>"
77
+ - - ">="
79
78
  - !ruby/object:Gem::Version
80
- version: '12.0'
79
+ version: '0'
81
80
  - !ruby/object:Gem::Dependency
82
81
  name: redis
83
82
  requirement: !ruby/object:Gem::Requirement
84
83
  requirements:
85
- - - "~>"
84
+ - - ">="
86
85
  - !ruby/object:Gem::Version
87
86
  version: '4.1'
87
+ - - "<"
88
+ - !ruby/object:Gem::Version
89
+ version: '6.0'
88
90
  type: :development
89
91
  prerelease: false
90
92
  version_requirements: !ruby/object:Gem::Requirement
91
93
  requirements:
92
- - - "~>"
94
+ - - ">="
93
95
  - !ruby/object:Gem::Version
94
96
  version: '4.1'
97
+ - - "<"
98
+ - !ruby/object:Gem::Version
99
+ version: '6.0'
95
100
  - !ruby/object:Gem::Dependency
96
101
  name: pg
97
102
  requirement: !ruby/object:Gem::Requirement
98
103
  requirements:
99
- - - "~>"
104
+ - - ">="
100
105
  - !ruby/object:Gem::Version
101
- version: '1.2'
106
+ version: '0'
102
107
  type: :development
103
108
  prerelease: false
104
109
  version_requirements: !ruby/object:Gem::Requirement
105
110
  requirements:
106
- - - "~>"
111
+ - - ">="
107
112
  - !ruby/object:Gem::Version
108
- version: '1.2'
113
+ version: '0'
109
114
  - !ruby/object:Gem::Dependency
110
115
  name: rack
111
116
  requirement: !ruby/object:Gem::Requirement
@@ -124,16 +129,16 @@ dependencies:
124
129
  name: rspec
125
130
  requirement: !ruby/object:Gem::Requirement
126
131
  requirements:
127
- - - "~>"
132
+ - - ">="
128
133
  - !ruby/object:Gem::Version
129
- version: '3.0'
134
+ version: '0'
130
135
  type: :development
131
136
  prerelease: false
132
137
  version_requirements: !ruby/object:Gem::Requirement
133
138
  requirements:
134
- - - "~>"
139
+ - - ">="
135
140
  - !ruby/object:Gem::Version
136
- version: '3.0'
141
+ version: '0'
137
142
  - !ruby/object:Gem::Dependency
138
143
  name: byebug
139
144
  requirement: !ruby/object:Gem::Requirement
@@ -190,7 +195,6 @@ dependencies:
190
195
  - - ">="
191
196
  - !ruby/object:Gem::Version
192
197
  version: '0'
193
- description:
194
198
  email:
195
199
  - tgx@discourse.org
196
200
  executables: []
@@ -217,10 +221,15 @@ files:
217
221
  - lib/rails_failover/active_record/middleware.rb
218
222
  - lib/rails_failover/active_record/railtie.rb
219
223
  - lib/rails_failover/redis.rb
220
- - lib/rails_failover/redis/connector.rb
221
- - lib/rails_failover/redis/handler.rb
224
+ - lib/rails_failover/redis/compat_4x/connector.rb
225
+ - lib/rails_failover/redis/compat_4x/handler.rb
226
+ - lib/rails_failover/redis/compat_4x/patches/client.rb
227
+ - lib/rails_failover/redis/compat_5x/client.rb
228
+ - lib/rails_failover/redis/compat_5x/config.rb
229
+ - lib/rails_failover/redis/compat_5x/handler.rb
230
+ - lib/rails_failover/redis/compat_5x/patches/client.rb
231
+ - lib/rails_failover/redis/handler_base.rb
222
232
  - lib/rails_failover/version.rb
223
- - lib/redis/patches/client.rb
224
233
  - makefile
225
234
  - postgresql.mk
226
235
  - rails_failover.gemspec
@@ -229,7 +238,6 @@ homepage: https://github.com/discourse/rails_failover
229
238
  licenses:
230
239
  - MIT
231
240
  metadata: {}
232
- post_install_message:
233
241
  rdoc_options: []
234
242
  require_paths:
235
243
  - lib
@@ -244,8 +252,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
244
252
  - !ruby/object:Gem::Version
245
253
  version: '0'
246
254
  requirements: []
247
- rubygems_version: 3.5.9
248
- signing_key:
255
+ rubygems_version: 3.6.2
249
256
  specification_version: 4
250
257
  summary: Failover for ActiveRecord and Redis
251
258
  test_files: []