rails_failover 0.8.1 → 2.0.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: 9b0f3e010743d83bd67b2cce785fde0952ba32c82c8c1ab5fef3aa56e5baa520
4
- data.tar.gz: 26cc2466069dfe48b691d8676078b3ed9ee3ce82e7a8d4447fa327436741fd39
3
+ metadata.gz: b769d81f278cffd6d960f9a43da2feb15d46e23dd35d2608dbef0fdb04688a70
4
+ data.tar.gz: a72536269e00455cf0506f68dfbbdf6c98a7b7c8a24064532a7219fa31b4bec6
5
5
  SHA512:
6
- metadata.gz: 5ce28a0d758dde383537bce455502edfcbf9388ad4c2835edc17b2e259156affc885153f728e39fa66c8c25d3f9dfea34a8f489b663e7738fc092dc6c76dccb8
7
- data.tar.gz: 0f0c08446e0e64c5eb6ee972e80e8776e58fbd32877254e50098a17a4d4475db64379d7ad237933b054e5eaf7c54df72785c69253e7d31be1b28d38028fcaf11
6
+ metadata.gz: 33f8835985676d80ba1701eda774322db0701040691e3164218bbf931901314a277ad63c926634f7ac6270f43e5899a7a90030b03f7ec53badb618bac0a187b3
7
+ data.tar.gz: a219d8441c37438bc1ea367fc99920cf3090728a5be482dd59a68f80a5690effa3f2a2a059085675980ed2e816e662ca3c485b12543437bde2801e07d4adfa6b
@@ -4,75 +4,100 @@ on:
4
4
  pull_request:
5
5
  push:
6
6
  branches:
7
- - master
8
7
  - main
9
8
 
10
9
  jobs:
11
- build:
10
+ lint:
12
11
  runs-on: ubuntu-latest
13
12
 
14
- env:
15
- BUILD_TYPE: ${{ matrix.build_types }}
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+
16
+ - name: Setup ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: '3.2'
20
+ bundler-cache: true
21
+
22
+ - name: Rubocop
23
+ run: bundle exec rubocop
24
+
25
+ - name: syntax_tree
26
+ if: ${{ !cancelled() }}
27
+ run: |
28
+ set -E
29
+ bundle exec stree check Gemfile rails_failover.gemspec $(git ls-files '*.rb')
30
+
31
+ redis:
32
+ name: 'Redis (Ruby ${{ matrix.ruby }})'
33
+ runs-on: ubuntu-latest
16
34
 
17
35
  strategy:
18
36
  fail-fast: false
19
37
  matrix:
20
- ruby: ["2.6", "2.7", "3.0", "3.1"]
21
- build_types: ["REDIS", "ACTIVERECORD"]
22
- include:
23
- - ruby: "3.1"
24
- build_types: "LINT"
38
+ ruby: ['3.0', '3.1', '3.2']
25
39
 
26
40
  steps:
27
- - uses: actions/checkout@v2
41
+ - uses: actions/checkout@v3
28
42
 
29
43
  - name: Setup ruby
30
44
  uses: ruby/setup-ruby@v1
31
45
  with:
32
46
  ruby-version: ${{ matrix.ruby }}
33
-
34
- - name: Setup bundler
35
- run: gem install bundler
36
-
37
- - name: Setup gems
38
- run: bundle install
39
-
40
- - name: Rubocop
41
- run: bundle exec rubocop
42
- if: env.BUILD_TYPE == 'LINT'
47
+ bundler-cache: true
43
48
 
44
49
  - name: Setup redis
45
50
  run: sudo apt-get install redis-server
46
- if: env.BUILD_TYPE == 'REDIS'
47
51
 
48
52
  - name: Redis specs
49
53
  run: bin/rspec redis
50
- if: env.BUILD_TYPE == 'REDIS'
51
54
 
52
- - name: Setup test app gems
53
- run: cd spec/support/dummy_app && bundle install
54
- if: env.BUILD_TYPE == 'ACTIVERECORD'
55
+ active_record:
56
+ runs-on: ubuntu-latest
57
+ name: 'ActiveRecord ~>${{ matrix.rails }} (Ruby ${{ matrix.ruby }})'
58
+
59
+ strategy:
60
+ fail-fast: false
61
+ matrix:
62
+ ruby: ['3.2', '3.1', '3.0']
63
+ rails: ['7.0.0']
64
+ include:
65
+ - ruby: '3.2'
66
+ rails: '6.1.0'
67
+ - ruby: '3.2'
68
+ rails: 'edge'
69
+
70
+ steps:
71
+ - uses: actions/checkout@v3
72
+
73
+ - name: Setup ruby
74
+ uses: ruby/setup-ruby@v1
75
+ with:
76
+ ruby-version: ${{ matrix.ruby }}
77
+
78
+ - name: Setup gems
79
+ run: bundle install
55
80
 
56
81
  - name: Setup postgres
57
82
  run: |
58
83
  make setup_pg
59
84
  make start_pg
60
- if: env.BUILD_TYPE == 'ACTIVERECORD'
61
85
 
62
86
  - name: ActiveRecord specs
87
+ env:
88
+ RAILS_VERSION: ${{ matrix.rails }}
63
89
  run: bin/rspec active_record
64
- if: env.BUILD_TYPE == 'ACTIVERECORD'
65
90
 
66
91
  publish:
67
- if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
68
- needs: build
92
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
93
+ needs: [lint, redis, active_record]
69
94
  runs-on: ubuntu-latest
70
95
 
71
96
  steps:
72
- - uses: actions/checkout@v2
97
+ - uses: actions/checkout@v3
73
98
 
74
99
  - name: Release Gem
75
- uses: discourse/publish-rubygems-action@v2
100
+ uses: discourse/publish-rubygems-action@v3
76
101
  env:
77
102
  RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
78
103
  GIT_EMAIL: team@discourse.org
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --require spec_helper
3
+ --format documentation
data/.rubocop.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  AllCops:
2
2
  Exclude:
3
3
  - spec/support/dummy_app/**/*
4
+ - vendor/**/*
4
5
 
5
6
  inherit_gem:
6
7
  rubocop-discourse: default.yml
data/.streerc ADDED
@@ -0,0 +1,2 @@
1
+ --print-width=100
2
+ --plugins=plugin/trailing_comma,disable_ternary
data/CHANGELOG.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Changelog
2
+
2
3
  All notable changes to this project will be documented in this file.
3
4
 
4
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
@@ -6,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
7
 
7
8
  ## [Unreleased]
8
9
 
10
+ ## [2.0.0] - 2023-05-16
11
+
12
+ - DEV: Compatibility with Rails 7.1+ (drop support for Rails 6.0 & ruby 2.7)
13
+
14
+ ## [1.0.0] - 2023-04-07
15
+
16
+ - DEV: Remove the support for Ruby < 2.7
17
+ - DEV: Compatibility with Rails 7.1+
18
+
9
19
  ## [0.8.0] - 2022-01-17
10
20
 
11
21
  - FEATURE: Compatibility with Rails 7.0+
@@ -20,7 +30,7 @@ No changes.
20
30
 
21
31
  ## [0.7.1] - 2021-04-14
22
32
 
23
- - FIX: Backward compatability with Rails 6.0
33
+ - FIX: Backward compatibility with Rails 6.0
24
34
 
25
35
  ## [0.7.0] - 2021-04-14
26
36
 
@@ -49,6 +59,7 @@ No changes.
49
59
  Previously, a replica failing would cause it to be added to the 'primaries_down' list. The fallback handler would then continuously try and fallback the replica to itself, looping forever, and meaning that fallback to primary would never happen.
50
60
 
51
61
  ## [0.6.0] - 2020-11-09
62
+
52
63
  - FEATURE: Run failover/fallback callbacks once for each backend
53
64
 
54
65
  Previously the failover callback would only fire when the first backend failed, and the fallback callback would only fire when the last backend recovered. Now both failover and fallback callbacks will be triggered for each backend. The key for each backend is also passed to the callbacks for consumption by consuming applications.
@@ -58,6 +69,7 @@ No changes.
58
69
  This is intended for consumption by monitoring systems (e.g. the Discourse prometheus exporter)
59
70
 
60
71
  ## [0.5.9] - 2020-11-06
72
+
61
73
  - FIX: Ignore errors from the redis socket shutdown call
62
74
 
63
75
  This can fail with various i/o errors, but in all cases we want the thread to continue closing the connection with the error, and all the other connections.
@@ -67,6 +79,7 @@ No changes.
67
79
  - FIX: Handle concurrency issues during redis disconnection (#10)
68
80
 
69
81
  This handles concurrency issues which can happen during redis failover/fallback:
82
+
70
83
  - Previously, 'subscribed' redis clients were skipped during the disconnect process. This is resolved by directly accessing the original_client from the ::Redis instance
71
84
  - Trying to acquire the mutex on a subscribed redis client is impossible, so the close operation would never complete. Now we send the shutdown() signal to the thread, then allow up to 1 second for the mutex to be released before we close the socket
72
85
  - Failover is almost always triggered inside a redis client mutex. Failover then has its own mutex, within which we attempted to acquire mutexes for all redis clients. This logic causes a deadlock when multiple clients failover simultaneously. Now, all disconnection is performed by the Redis::Handler failover thread, outside of any other mutexes. To make this safe, the primary/replica state is stored in the connection driver, and disconnect_clients is updated to specifically target primary/replica connections.
@@ -107,4 +120,5 @@ No changes.
107
120
  ## [0.5.2] - 2020-06-23
108
121
 
109
122
  ### Changed
123
+
110
124
  - FIX: Only rescue from connection errors.
data/Gemfile CHANGED
@@ -3,12 +3,3 @@ source "https://rubygems.org"
3
3
 
4
4
  # Specify your gem's dependencies in rails_failover.gemspec
5
5
  gemspec
6
-
7
- gem "rake", "~> 12.0"
8
- gem "rspec", "~> 3.0"
9
- gem 'rubocop-discourse'
10
- gem 'byebug'
11
- gem 'redis', '~> 4.1'
12
- gem 'pg', '~> 1.2'
13
- gem 'activerecord', '~> 6.0'
14
- gem 'rack'
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Automatic failover and recovery for primary/replica setup for:
4
4
 
5
5
  1. Redis
6
- 1. ActiveRecord (PostgreSQL/MySQL Adapters)
6
+ 2. ActiveRecord (PostgreSQL/MySQL Adapters)
7
7
 
8
8
  ## Installation
9
9
 
@@ -29,7 +29,7 @@ In `config/application.rb` add `require 'rails_failover/active_record'` after `r
29
29
 
30
30
  In your database configuration, simply add `replica_host` and `replica_port` to your database configuration.
31
31
 
32
- ```
32
+ ```yml
33
33
  production:
34
34
  host: <primary db server host>
35
35
  port: <primary db server port>
@@ -37,11 +37,12 @@ production:
37
37
  replica_port: <replica db server port>
38
38
  ```
39
39
 
40
- The gem will automatically create an `ActiveRecord::ConnectionAdapters::ConnectionHandler` with the `ActiveRecord::Base.reading_role` as the `handler_key`.
40
+ The gem will automatically create a role (using `ActiveRecord.reading_role`) on
41
+ the default `ActiveRecord` connection handler.
41
42
 
42
43
  #### Failover/Fallback Hooks
43
44
 
44
- ```
45
+ ```ruby
45
46
  RailsFailover::ActiveRecord.on_failover do
46
47
  # Enable readonly mode
47
48
  end
@@ -53,9 +54,7 @@ end
53
54
 
54
55
  #### Multiple connection handlers
55
56
 
56
- Note: This API is unstable and is likely to change when Rails 6.1 is released with sharding support.
57
-
58
- ```
57
+ ```yml
59
58
  # config/database.yml
60
59
 
61
60
  production:
@@ -72,20 +71,20 @@ production:
72
71
 
73
72
  # In your ActiveRecord base model or model.
74
73
 
75
- connects_to database: { writing: :primary, second_database_writing: :second_database_writing
74
+ connects_to database: { writing: :primary, second_database_writing: :second_database_writing }
76
75
  ```
77
76
 
78
77
  ### Redis
79
78
 
80
79
  Add `require 'rails_failover/redis'` before creating a `Redis` instance.
81
80
 
82
- ```
83
- Redis.new(host: "127.0.0.1", port: 6379, replica_host: "127.0.0.1", replica_port: 6380, connector: RailsFailover::Redis::Connector))
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)
84
83
  ```
85
84
 
86
85
  Callbacks can be registered when the primary connection is down and when it is up.
87
86
 
88
- ```
87
+ ```ruby
89
88
  RailsFailover::Redis.on_failover_callback do
90
89
  # Switch site to read-only mode
91
90
  end
@@ -108,8 +107,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
108
107
  The ActiveRecord failover tests are run against a dummy Rails server. Run the following commands to run the test:
109
108
 
110
109
  1. `make setup_pg`
111
- 1. `make start_pg`
112
- 1. `bin/rspec active_record`. You may also run the tests with more unicorn workers by adding the `UNICORN_WORKERS` env variable.
110
+ 2. `make start_pg`
111
+ 3. `bin/rspec active_record`. You may also run the tests with more unicorn workers by adding the `UNICORN_WORKERS` env variable.
113
112
 
114
113
  #### Redis
115
114
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require 'singleton'
3
- require 'monitor'
4
- require 'concurrent'
2
+ require "singleton"
3
+ require "monitor"
4
+ require "concurrent"
5
5
 
6
6
  module RailsFailover
7
7
  module ActiveRecord
@@ -9,7 +9,7 @@ module RailsFailover
9
9
  include Singleton
10
10
  include MonitorMixin
11
11
 
12
- VERIFY_FREQUENCY_BUFFER_PRECENT = 20
12
+ VERIFY_FREQUENCY_BUFFER_PERCENT = 20
13
13
 
14
14
  def initialize
15
15
  @primaries_down = Concurrent::Map.new
@@ -50,26 +50,20 @@ module RailsFailover
50
50
 
51
51
  def initiate_fallback_to_primary
52
52
  frequency = RailsFailover::ActiveRecord.verify_primary_frequency_seconds
53
- sleep(frequency * ((rand(VERIFY_FREQUENCY_BUFFER_PRECENT) + 100) / 100.0))
53
+ sleep(frequency * ((rand(VERIFY_FREQUENCY_BUFFER_PERCENT) + 100) / 100.0))
54
54
 
55
55
  active_handler_keys = []
56
56
 
57
57
  primaries_down.keys.each do |handler_key|
58
- connection_handler = ::ActiveRecord::Base.connection_handlers[handler_key]
59
-
60
- connection_pool = connection_handler.retrieve_connection_pool(spec_name)
61
- if connection_pool.respond_to?(:db_config)
62
- config = connection_pool.db_config.configuration_hash
63
- adapter_method = connection_pool.db_config.adapter_method
64
- else
65
- config = connection_pool.spec.config
66
- adapter_method = connection_pool.spec.adapter_method
67
- end
68
58
  logger.debug "#{Process.pid} Checking server for '#{handler_key} #{spec_name}'..."
69
59
  connection_active = false
70
60
 
71
61
  begin
72
- connection = ::ActiveRecord::Base.public_send(adapter_method, config)
62
+ connection =
63
+ ::ActiveRecord::Base.connection_handler.retrieve_connection(
64
+ spec_name,
65
+ role: handler_key,
66
+ )
73
67
  connection_active = connection.active?
74
68
  rescue => e
75
69
  logger.debug "#{Process.pid} Connection to server for '#{handler_key} #{spec_name}' failed with '#{e.message}'"
@@ -83,9 +77,7 @@ module RailsFailover
83
77
  end
84
78
  end
85
79
 
86
- active_handler_keys.each do |handler_key|
87
- primary_up(handler_key)
88
- end
80
+ active_handler_keys.each { |handler_key| primary_up(handler_key) }
89
81
  end
90
82
 
91
83
  def all_primaries_up
@@ -108,10 +100,11 @@ module RailsFailover
108
100
 
109
101
  def primaries_down
110
102
  ancestor_pids = nil
111
- value = @primaries_down.compute_if_absent(Process.pid) do
112
- ancestor_pids = @primaries_down.keys
113
- @primaries_down.values.first || Concurrent::Map.new
114
- end
103
+ value =
104
+ @primaries_down.compute_if_absent(Process.pid) do
105
+ ancestor_pids = @primaries_down.keys
106
+ @primaries_down.values.first || Concurrent::Map.new
107
+ end
115
108
 
116
109
  ancestor_pids&.each do |pid|
117
110
  @primaries_down.delete(pid)&.each_key { |key| verify_primary(key) }
@@ -4,20 +4,18 @@ module RailsFailover
4
4
  module ActiveRecord
5
5
  class Interceptor
6
6
  def self.adapter_errors
7
- @adapter_errors ||= begin
8
- if defined?(::PG)
9
- [::PG::UnableToSend, ::PG::ConnectionBad]
10
- elsif defined?(::Mysql2)
11
- [::Mysql2::Error::ConnectionError]
7
+ @adapter_errors ||=
8
+ begin
9
+ if defined?(::PG)
10
+ [::PG::UnableToSend, ::PG::ConnectionBad]
11
+ elsif defined?(::Mysql2)
12
+ [::Mysql2::Error::ConnectionError]
13
+ end
12
14
  end
13
- end
14
15
  end
15
16
 
16
17
  def self.handle(request, exception)
17
- verify_primary(
18
- exception,
19
- request.env[Middleware::WRITING_ROLE_HEADER]
20
- )
18
+ verify_primary(exception, request.env[Middleware::WRITING_ROLE_HEADER])
21
19
  end
22
20
 
23
21
  def self.verify_primary(exception, writing_role)
@@ -29,11 +27,7 @@ module RailsFailover
29
27
  end
30
28
 
31
29
  def self.resolve_cause(exception)
32
- if exception.cause
33
- resolve_cause(exception.cause)
34
- else
35
- exception
36
- end
30
+ exception.cause ? resolve_cause(exception.cause) : exception
37
31
  end
38
32
  end
39
33
 
@@ -50,14 +44,18 @@ module RailsFailover
50
44
  end
51
45
 
52
46
  def call(env)
53
- current_role = ::ActiveRecord::Base.current_role || ::ActiveRecord::Base.writing_role
54
- is_writing_role = current_role.to_s.end_with?(::ActiveRecord::Base.writing_role.to_s)
47
+ current_role = ::ActiveRecord::Base.current_role || RailsFailover::ActiveRecord.writing_role
48
+ is_writing_role = current_role.to_s.end_with?(RailsFailover::ActiveRecord.writing_role.to_s)
55
49
  writing_role = resolve_writing_role(current_role, is_writing_role)
56
50
 
57
51
  role =
58
- if primary_down = self.class.force_reading_role_callback&.call(env) || Handler.instance.primary_down?(writing_role)
52
+ if self.class.force_reading_role_callback&.call(env) ||
53
+ Handler.instance.primary_down?(writing_role)
59
54
  reading_role = resolve_reading_role(current_role, is_writing_role)
60
- ensure_reading_connection_established!(writing_role: writing_role, reading_role: reading_role)
55
+ ensure_reading_connection_established!(
56
+ writing_role: writing_role,
57
+ reading_role: reading_role,
58
+ )
61
59
  reading_role
62
60
  else
63
61
  writing_role
@@ -76,42 +74,39 @@ module RailsFailover
76
74
  private
77
75
 
78
76
  def ensure_reading_connection_established!(writing_role:, reading_role:)
79
- ::ActiveRecord::Base.connection_handlers[reading_role] ||= begin
80
- handler = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new
81
-
82
- ::ActiveRecord::Base.connection_handlers[writing_role].connection_pools.each do |pool|
83
- if pool.respond_to?(:db_config)
84
- config = pool.db_config.configuration_hash
85
- else
86
- config = pool.spec.config
87
- end
88
- ::RailsFailover::ActiveRecord.establish_reading_connection(handler, config)
77
+ connection_handler = ::ActiveRecord::Base.connection_handler
78
+ connection_handler
79
+ .connection_pools(writing_role)
80
+ .each do |connection_pool|
81
+ config = connection_pool.db_config.configuration_hash
82
+ RailsFailover::ActiveRecord.establish_reading_connection(
83
+ connection_handler,
84
+ config,
85
+ role: reading_role,
86
+ )
89
87
  end
90
-
91
- handler
92
- end
93
88
  end
94
89
 
95
90
  def resolve_writing_role(current_role, is_writing_role)
96
- if is_writing_role
97
- current_role
98
- else
99
- current_role.to_s.sub(
100
- /#{::ActiveRecord::Base.reading_role}$/,
101
- ::ActiveRecord::Base.writing_role.to_s
102
- ).to_sym
103
- end
91
+ return current_role if is_writing_role
92
+ current_role
93
+ .to_s
94
+ .sub(
95
+ /#{RailsFailover::ActiveRecord.reading_role}$/,
96
+ RailsFailover::ActiveRecord.writing_role.to_s,
97
+ )
98
+ .to_sym
104
99
  end
105
100
 
106
101
  def resolve_reading_role(current_role, is_writing_role)
107
- if is_writing_role
108
- current_role.to_s.sub(
109
- /#{::ActiveRecord::Base.writing_role}$/,
110
- ::ActiveRecord::Base.reading_role.to_s
111
- ).to_sym
112
- else
113
- current_role
114
- end
102
+ return current_role unless is_writing_role
103
+ current_role
104
+ .to_s
105
+ .sub(
106
+ /#{RailsFailover::ActiveRecord.writing_role}$/,
107
+ RailsFailover::ActiveRecord.reading_role.to_s,
108
+ )
109
+ .to_sym
115
110
  end
116
111
  end
117
112
  end
@@ -4,48 +4,18 @@ module RailsFailover
4
4
  module ActiveRecord
5
5
  class Railtie < ::Rails::Railtie
6
6
  initializer "rails_failover.init", after: "active_record.initialize_database" do |app|
7
-
8
- # AR 6.0 / 6.1 compat
9
- config =
10
- if ::ActiveRecord::Base.respond_to? :connection_db_config
11
- ::ActiveRecord::Base.connection_db_config.configuration_hash
12
- else
13
- ::ActiveRecord::Base.connection_config
14
- end
15
-
16
7
  app.config.active_record_rails_failover = false
17
-
18
- if !!(config[:replica_host] && config[:replica_port])
19
- app.config.active_record_rails_failover = true
20
-
21
- ::ActiveSupport.on_load(:active_record) do
22
- Handler.instance
23
-
24
- # We are doing this manually for now since we're awaiting Rails 6.1 to be released which will
25
- # have more stable ActiveRecord APIs for handling multiple databases with different roles.
26
- ::ActiveRecord::Base.connection_handlers[::ActiveRecord::Base.reading_role] =
27
- ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new
28
-
29
- ::ActiveRecord::Base.connection_handlers[::ActiveRecord::Base.writing_role].connection_pools.each do |connection_pool|
30
- if connection_pool.respond_to?(:db_config)
31
- config = connection_pool.db_config.configuration_hash
32
- else
33
- config = connection_pool.spec.config
34
- end
35
- RailsFailover::ActiveRecord.establish_reading_connection(
36
- ::ActiveRecord::Base.connection_handlers[::ActiveRecord::Base.reading_role],
37
- config
38
- )
39
- end
40
-
41
- begin
42
- ::ActiveRecord::Base.connection
43
- rescue ::ActiveRecord::NoDatabaseError
44
- # Do nothing since database hasn't been created
45
- rescue ::PG::Error, ::ActiveRecord::ConnectionNotEstablished
46
- Handler.instance.verify_primary(::ActiveRecord::Base.writing_role)
47
- ::ActiveRecord::Base.connection_handler = ::ActiveRecord::Base.lookup_connection_handler(:reading)
48
- end
8
+ config = RailsFailover::ActiveRecord.config
9
+ break unless config[:replica_host] && config[:replica_port]
10
+
11
+ app.config.active_record_rails_failover = true
12
+ ::ActiveSupport.on_load(:active_record) do
13
+ begin
14
+ ::ActiveRecord::Base.connection
15
+ rescue ::ActiveRecord::NoDatabaseError
16
+ # Do nothing since database hasn't been created
17
+ rescue ::PG::Error, ::ActiveRecord::ConnectionNotEstablished
18
+ Handler.instance.verify_primary(RailsFailover::ActiveRecord.writing_role)
49
19
  end
50
20
  end
51
21
  end
@@ -57,14 +27,13 @@ module RailsFailover
57
27
  end
58
28
 
59
29
  if !skip_middleware?(app.config)
60
- app.middleware.unshift(::RailsFailover::ActiveRecord::Middleware)
30
+ app.middleware.unshift(RailsFailover::ActiveRecord::Middleware)
61
31
  end
62
32
  end
63
33
  end
64
34
 
65
35
  def skip_middleware?(config)
66
- return false if !config.respond_to?(:skip_rails_failover_active_record_middleware)
67
- config.skip_rails_failover_active_record_middleware
36
+ config.try(:skip_rails_failover_active_record_middleware)
68
37
  end
69
38
  end
70
39
  end
@@ -1,65 +1,79 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record'
3
+ require "active_record"
4
4
 
5
- if defined?(::Rails)
6
- require_relative 'active_record/railtie'
7
- end
5
+ require_relative "active_record/railtie" if defined?(::Rails)
8
6
 
9
- require_relative 'active_record/middleware'
10
- require_relative 'active_record/handler'
7
+ require_relative "active_record/middleware"
8
+ require_relative "active_record/handler"
11
9
 
12
10
  module RailsFailover
13
11
  module ActiveRecord
14
- def self.logger=(logger)
15
- @logger = logger
16
- end
12
+ class << self
13
+ def config
14
+ ::ActiveRecord::Base.connection_db_config.configuration_hash
15
+ end
17
16
 
18
- def self.logger
19
- @logger || Rails.logger
20
- end
17
+ def logger=(logger)
18
+ @logger = logger
19
+ end
21
20
 
22
- def self.verify_primary_frequency_seconds=(seconds)
23
- @verify_primary_frequency_seconds = seconds
24
- end
21
+ def logger
22
+ @logger || Rails.logger
23
+ end
25
24
 
26
- def self.verify_primary_frequency_seconds
27
- @verify_primary_frequency_seconds || 5
28
- end
25
+ def verify_primary_frequency_seconds=(seconds)
26
+ @verify_primary_frequency_seconds = seconds
27
+ end
29
28
 
30
- def self.establish_reading_connection(handler, config)
29
+ def verify_primary_frequency_seconds
30
+ @verify_primary_frequency_seconds || 5
31
+ end
31
32
 
32
- if config[:replica_host] && config[:replica_port]
33
+ def establish_reading_connection(handler, config, role: reading_role)
34
+ return unless config[:replica_host] && config[:replica_port]
33
35
  replica_config = config.dup
34
36
  replica_config[:host] = replica_config.delete(:replica_host)
35
37
  replica_config[:port] = replica_config.delete(:replica_port)
36
38
  replica_config[:replica] = true
37
- handler.establish_connection(replica_config)
39
+ handler.establish_connection(replica_config, role: role)
38
40
  end
39
- end
40
41
 
41
- def self.register_force_reading_role_callback(&block)
42
- Middleware.force_reading_role_callback = block
43
- end
42
+ def register_force_reading_role_callback(&block)
43
+ Middleware.force_reading_role_callback = block
44
+ end
44
45
 
45
- def self.on_failover(&block)
46
- @on_failover_callback = block
47
- end
46
+ def on_failover(&block)
47
+ @on_failover_callback = block
48
+ end
48
49
 
49
- def self.on_failover_callback!(key)
50
- @on_failover_callback&.call(key)
51
- rescue => e
52
- logger.warn("RailsFailover::ActiveRecord.on_failover failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}")
53
- end
50
+ def on_failover_callback!(key)
51
+ @on_failover_callback&.call(key)
52
+ rescue => e
53
+ logger.warn(
54
+ "RailsFailover::ActiveRecord.on_failover failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
55
+ )
56
+ end
54
57
 
55
- def self.on_fallback(&block)
56
- @on_fallback_callback = block
57
- end
58
+ def on_fallback(&block)
59
+ @on_fallback_callback = block
60
+ end
61
+
62
+ def on_fallback_callback!(key)
63
+ @on_fallback_callback&.call(key)
64
+ rescue => e
65
+ logger.warn(
66
+ "RailsFailover::ActiveRecord.on_fallback failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
67
+ )
68
+ end
58
69
 
59
- def self.on_fallback_callback!(key)
60
- @on_fallback_callback&.call(key)
61
- rescue => e
62
- logger.warn("RailsFailover::ActiveRecord.on_fallback failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}")
70
+ def reading_role
71
+ ::ActiveRecord.try(:reading_role) || ::ActiveRecord::Base.reading_role
72
+ end
73
+
74
+ def writing_role
75
+ ::ActiveRecord.try(:writing_role) || ::ActiveRecord::Base.writing_role
76
+ end
63
77
  end
64
78
  end
65
79
  end
@@ -1,22 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'handler'
3
+ require_relative "handler"
4
4
 
5
5
  module RailsFailover
6
6
  class Redis
7
7
  class Connector < ::Redis::Client::Connector
8
8
  def initialize(options)
9
- orignal_driver = options[:driver]
9
+ original_driver = options[:driver]
10
10
  options[:primary_host] = options[:host]
11
11
  options[:primary_port] = options[:port]
12
12
 
13
13
  options[:driver] = Class.new(options[:driver]) do
14
14
  def self.connect(options)
15
- is_primary = (options[:host] == options[:primary_host]) &&
16
- (options[:port] == options[:primary_port])
17
- super(options).tap do |conn|
18
- conn.rails_failover_role = is_primary ? PRIMARY : REPLICA
19
- end
15
+ is_primary =
16
+ (options[:host] == options[:primary_host]) &&
17
+ (options[:port] == options[:primary_port])
18
+ super(options).tap { |conn| conn.rails_failover_role = is_primary ? PRIMARY : REPLICA }
20
19
  rescue ::Redis::TimeoutError,
21
20
  SocketError,
22
21
  Errno::EADDRNOTAVAIL,
@@ -27,7 +26,6 @@ module RailsFailover
27
26
  Errno::ENOENT,
28
27
  Errno::ETIMEDOUT,
29
28
  Errno::EINVAL => e
30
-
31
29
  Handler.instance.verify_primary(options) if is_primary
32
30
  raise e
33
31
  end
@@ -40,7 +38,7 @@ module RailsFailover
40
38
  end
41
39
  end
42
40
 
43
- options[:original_driver] = orignal_driver
41
+ options[:original_driver] = original_driver
44
42
  options.delete(:connector)
45
43
  options[:id] ||= "#{options[:host]}:#{options[:port]}"
46
44
  @replica_options = replica_options(options)
@@ -48,11 +46,7 @@ module RailsFailover
48
46
  end
49
47
 
50
48
  def resolve
51
- if Handler.instance.primary_down?(@options)
52
- @replica_options
53
- else
54
- @options
55
- end
49
+ Handler.instance.primary_down?(@options) ? @replica_options : @options
56
50
  end
57
51
 
58
52
  def check(client)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'monitor'
4
- require 'singleton'
5
- require 'concurrent'
3
+ require "monitor"
4
+ require "singleton"
5
+ require "concurrent"
6
6
 
7
7
  module RailsFailover
8
8
  class Redis
@@ -12,7 +12,7 @@ module RailsFailover
12
12
 
13
13
  PRIMARY_ROLE_STATUS = "role:master"
14
14
  PRIMARY_LOADED_STATUS = "loading:0"
15
- VERIFY_FREQUENCY_BUFFER_PRECENT = 20
15
+ VERIFY_FREQUENCY_BUFFER_PERCENT = 20
16
16
  SOFT_DISCONNECT_TIMEOUT_SECONDS = 1
17
17
  SOFT_DISCONNECT_POLL_SECONDS = 0.05
18
18
 
@@ -67,7 +67,7 @@ module RailsFailover
67
67
 
68
68
  def try_fallback_to_primary
69
69
  frequency = RailsFailover::Redis.verify_primary_frequency_seconds
70
- sleep(frequency * ((rand(VERIFY_FREQUENCY_BUFFER_PRECENT) + 100) / 100.0))
70
+ sleep(frequency * ((rand(VERIFY_FREQUENCY_BUFFER_PERCENT) + 100) / 100.0))
71
71
 
72
72
  active_primaries_keys = {}
73
73
 
@@ -114,10 +114,11 @@ module RailsFailover
114
114
 
115
115
  def primaries_down
116
116
  ancestor_pids = nil
117
- value = @primaries_down.compute_if_absent(Process.pid) do
118
- ancestor_pids = @primaries_down.keys
119
- @primaries_down.values.first || Concurrent::Map.new
120
- end
117
+ value =
118
+ @primaries_down.compute_if_absent(Process.pid) do
119
+ ancestor_pids = @primaries_down.keys
120
+ @primaries_down.values.first || Concurrent::Map.new
121
+ end
121
122
 
122
123
  ancestor_pids&.each do |pid|
123
124
  @primaries_down.delete(pid)&.each { |id, options| verify_primary(options) }
@@ -132,10 +133,11 @@ module RailsFailover
132
133
 
133
134
  def clients
134
135
  ancestor_pids = nil
135
- clients_for_pid = @clients.compute_if_absent(Process.pid) do
136
- ancestor_pids = @clients.keys
137
- Concurrent::Map.new
138
- end
136
+ clients_for_pid =
137
+ @clients.compute_if_absent(Process.pid) do
138
+ ancestor_pids = @clients.keys
139
+ Concurrent::Map.new
140
+ end
139
141
  ancestor_pids&.each { |k| @clients.delete(k) }
140
142
  clients_for_pid
141
143
  end
@@ -149,9 +151,8 @@ module RailsFailover
149
151
  def disconnect_clients(options, role)
150
152
  id = options[:id]
151
153
 
152
- matched_clients = clients_for_id(id)&.keys
153
- &.filter { |c| c.connection.rails_failover_role == role }
154
- &.to_set
154
+ matched_clients =
155
+ clients_for_id(id)&.keys&.filter { |c| c.connection.rails_failover_role == role }&.to_set
155
156
 
156
157
  return if matched_clients.nil? || matched_clients.empty?
157
158
 
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'redis'
3
+ require "redis"
4
4
 
5
- supported_version = '4'
5
+ supported_version = "4"
6
6
 
7
7
  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
11
  require_relative "../redis/patches/client"
12
- require_relative 'redis/connector'
12
+ require_relative "redis/connector"
13
13
 
14
14
  module RailsFailover
15
15
  class Redis
@@ -43,7 +43,9 @@ module RailsFailover
43
43
  def self.on_failover_callback!(key)
44
44
  @on_failover_callback&.call(key)
45
45
  rescue => e
46
- logger.warn("RailsFailover::Redis.on_failover failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}")
46
+ logger.warn(
47
+ "RailsFailover::Redis.on_failover failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
48
+ )
47
49
  end
48
50
 
49
51
  def self.on_fallback(&block)
@@ -53,7 +55,9 @@ module RailsFailover
53
55
  def self.on_fallback_callback!(key)
54
56
  @on_fallback_callback&.call(key)
55
57
  rescue => e
56
- logger.warn("RailsFailover::Redis.on_fallback failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}")
58
+ logger.warn(
59
+ "RailsFailover::Redis.on_fallback failed: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}",
60
+ )
57
61
  end
58
62
 
59
63
  # For testing
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsFailover
4
- VERSION = "0.8.1"
4
+ VERSION = "2.0.0"
5
5
  end
@@ -3,5 +3,6 @@
3
3
  require "rails_failover/version"
4
4
 
5
5
  module RailsFailover
6
- class Error < StandardError; end
6
+ class Error < StandardError
7
+ end
7
8
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'redis'
3
+ require "redis"
4
4
 
5
5
  # See https://github.com/redis/redis-rb/pull/908
6
6
  class Redis::Client
data/makefile CHANGED
@@ -9,13 +9,13 @@ test_active_record:
9
9
  @ACTIVE_RECORD=1 bundle exec rspec --tag type:active_record ${RSPEC_PATH}
10
10
 
11
11
  setup_dummy_rails_server:
12
- @cd spec/support/dummy_app && bundle install --quiet && yarn install && RAILS_ENV=production $(BUNDLER_BIN) exec rails db:create db:migrate db:seed
12
+ @cd spec/support/dummy_app && BUNDLE_GEMFILE=Gemfile bundle install --quiet && yarn install && BUNDLE_GEMFILE=Gemfile RAILS_ENV=production $(BUNDLER_BIN) exec rails db:create db:migrate db:seed
13
13
 
14
14
  start_dummy_rails_server:
15
- @cd spec/support/dummy_app && BUNDLE_GEMFILE=Gemfile UNICORN_WORKERS=5 SECRET_KEY_BASE=somekey bundle exec unicorn -c config/unicorn.conf.rb -D -E production
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
16
 
17
17
  stop_dummy_rails_server:
18
18
  @kill -TERM $(shell cat spec/support/dummy_app/tmp/pids/unicorn.pid)
19
19
 
20
20
  teardown_dummy_rails_server:
21
- @cd spec/support/dummy_app && (! (bundle check > /dev/null 2>&1) || DISABLE_DATABASE_ENVIRONMENT_CHECK=1 RAILS_ENV=production $(BUNDLER_BIN) exec rails db:drop)
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)
@@ -1,29 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lib/rails_failover/version'
3
+ require_relative "lib/rails_failover/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "rails_failover"
7
- spec.version = RailsFailover::VERSION
8
- spec.authors = ["Alan Tan"]
9
- spec.email = ["tgx@discourse.org"]
6
+ spec.name = "rails_failover"
7
+ spec.version = RailsFailover::VERSION
8
+ spec.authors = ["Alan Tan"]
9
+ spec.email = ["tgx@discourse.org"]
10
10
 
11
- spec.summary = %q{Failover for ActiveRecord and Redis}
12
- spec.homepage = "https://github.com/discourse/rails_failover"
13
- spec.license = "MIT"
14
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
11
+ spec.summary = "Failover for ActiveRecord and Redis"
12
+ spec.homepage = "https://github.com/discourse/rails_failover"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
15
15
 
16
16
  # Specify which files should be added to the gem when it is released.
17
17
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
- end
21
- spec.bindir = "exe"
22
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.files =
19
+ Dir.chdir(File.expand_path("..", __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
24
  spec.require_paths = ["lib"]
24
25
 
25
- spec.add_dependency "activerecord", "> 6.0", "< 7.1"
26
- spec.add_dependency "railties", "> 6.0", "< 7.1"
27
-
26
+ spec.add_dependency "activerecord", ">= 6.1", "<= 7.1"
27
+ spec.add_dependency "railties", ">= 6.1", "<= 7.1"
28
28
  spec.add_dependency "concurrent-ruby"
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"
33
+ spec.add_development_dependency "rack"
34
+ spec.add_development_dependency "rspec", "~> 3.0"
35
+ spec.add_development_dependency "byebug"
36
+ spec.add_development_dependency "rubocop-discourse"
37
+ spec.add_development_dependency "syntax_tree"
38
+ spec.add_development_dependency "syntax_tree-disable_ternary"
29
39
  end
metadata CHANGED
@@ -1,53 +1,53 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_failover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Tan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-15 00:00:00.000000000 Z
11
+ date: 2023-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6.0'
20
- - - "<"
19
+ version: '6.1'
20
+ - - "<="
21
21
  - !ruby/object:Gem::Version
22
22
  version: '7.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">"
27
+ - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '6.0'
30
- - - "<"
29
+ version: '6.1'
30
+ - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '7.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: railties
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ">"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '6.0'
40
- - - "<"
39
+ version: '6.1'
40
+ - - "<="
41
41
  - !ruby/object:Gem::Version
42
42
  version: '7.1'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - ">"
47
+ - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: '6.0'
50
- - - "<"
49
+ version: '6.1'
50
+ - - "<="
51
51
  - !ruby/object:Gem::Version
52
52
  version: '7.1'
53
53
  - !ruby/object:Gem::Dependency
@@ -64,6 +64,132 @@ dependencies:
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
66
  version: '0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: rake
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '12.0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '12.0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: redis
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '4.1'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '4.1'
95
+ - !ruby/object:Gem::Dependency
96
+ name: pg
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '1.2'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '1.2'
109
+ - !ruby/object:Gem::Dependency
110
+ name: rack
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: rspec
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '3.0'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '3.0'
137
+ - !ruby/object:Gem::Dependency
138
+ name: byebug
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ - !ruby/object:Gem::Dependency
152
+ name: rubocop-discourse
153
+ requirement: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ type: :development
159
+ prerelease: false
160
+ version_requirements: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ - !ruby/object:Gem::Dependency
166
+ name: syntax_tree
167
+ requirement: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ type: :development
173
+ prerelease: false
174
+ version_requirements: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ - !ruby/object:Gem::Dependency
180
+ name: syntax_tree-disable_ternary
181
+ requirement: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
67
193
  description:
68
194
  email:
69
195
  - tgx@discourse.org
@@ -75,6 +201,7 @@ files:
75
201
  - ".gitignore"
76
202
  - ".rspec"
77
203
  - ".rubocop.yml"
204
+ - ".streerc"
78
205
  - CHANGELOG.md
79
206
  - CODE_OF_CONDUCT.md
80
207
  - Gemfile
@@ -110,14 +237,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
110
237
  requirements:
111
238
  - - ">="
112
239
  - !ruby/object:Gem::Version
113
- version: 2.5.0
240
+ version: 3.0.0
114
241
  required_rubygems_version: !ruby/object:Gem::Requirement
115
242
  requirements:
116
243
  - - ">="
117
244
  - !ruby/object:Gem::Version
118
245
  version: '0'
119
246
  requirements: []
120
- rubygems_version: 3.1.6
247
+ rubygems_version: 3.4.10
121
248
  signing_key:
122
249
  specification_version: 4
123
250
  summary: Failover for ActiveRecord and Redis