rails_failover 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []