active_record_host_pool 0.12.0 → 1.0.3

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: 902e5e0cb2f51303355efa11fe7d25d357d6c9560310cab1e3e6bc5c0ad1758b
4
- data.tar.gz: 9584c62c0572915b4de0aff99b2449a787c6236d3828e3858b6a6c2079ef1882
3
+ metadata.gz: 5238d6f59f78811a9e9972e613562d674d1137604438b46d0ef3b9a3eb1fa437
4
+ data.tar.gz: 937e6d46772dc13b3b6faa62841053252aae17f92f8ef205b63927345408ed65
5
5
  SHA512:
6
- metadata.gz: 400906a1082b28c51d7bbcd3efffed956e4e26ce46c8bc2e71a73431ce1028eafcbfef06a0b3a220647ea3e5865b1d9ac8b2dd34960bba76d7ca059339daaf0a
7
- data.tar.gz: 8094cbe8a0497f5d16cb7caa27953d4e78e2945cd46f53e00912654b2fff7cb447c8dc323b0faeebc74f3c425bd9e6a2b86f4546485e46171616a3cd2b6f10e3
6
+ metadata.gz: bdbd8abf041ad7960996bbd0432fc19c90c0ae0c41328069798c401929cca9c3b9dd510f7160ab833895962b12e86937bf8eb592d851bea2197b94fcdf57b95a
7
+ data.tar.gz: '068fff2a8be21b0e1ca192a49967c352d90b5c81aae27c11f36128ec825b4c03a9087f56269b10ca2cf406ddea445dc48662110284f1a3870eb4d48df0ceba7b'
data/Readme.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build status](https://circleci.com/gh/zendesk/active_record_host_pool.svg?style=svg)](https://circleci.com/gh/zendesk/active_record_host_pool)
1
+ [![Build Status](https://github.com/zendesk/active_record_host_pool/workflows/CI/badge.svg)](https://github.com/zendesk/active_record_host_pool/actions?query=workflow%3ACI)
2
2
 
3
3
  # ActiveRecord host pooling
4
4
 
@@ -24,6 +24,14 @@ You need a local user called 'john-doe'.
24
24
  GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,INDEX ON *.* TO 'john-doe'@'localhost';
25
25
  FLUSH PRIVILEGES;
26
26
 
27
+ With mysql running locally, run
28
+
29
+ BUNDLE_GEMFILE=gemfiles/rails5.2.gemfile bundle exec rake test
30
+
31
+ Or
32
+
33
+ BUNDLE_GEMFILE=gemfiles/rails5.2.gemfile ruby test/test_arhp.rb --seed 19911 --verbose
34
+
27
35
  ## Copyright
28
36
 
29
37
  Copyright (c) 2011 Zendesk. See MIT-LICENSE for details.
@@ -4,6 +4,7 @@ require 'active_record'
4
4
  require 'active_record/base'
5
5
  require 'active_record/connection_adapters/abstract_adapter'
6
6
 
7
+ require 'active_record_host_pool/clear_query_cache_patch'
7
8
  require 'active_record_host_pool/connection_proxy'
8
9
  require 'active_record_host_pool/pool_proxy'
9
10
  require 'active_record_host_pool/connection_adapter_mixin'
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ if ActiveRecord.version >= Gem::Version.new('6.0')
4
+ module ActiveRecordHostPool
5
+ # ActiveRecord 6.0 introduced multiple database support. With that, an update
6
+ # has been made in https://github.com/rails/rails/pull/35089 to ensure that
7
+ # all query caches are cleared across connection handlers and pools. If you
8
+ # write on one connection, the other connection will have the update that
9
+ # occurred.
10
+ #
11
+ # This broke ARHP which implements its own pool, allowing you to access
12
+ # multiple databases with the same connection (e.g. 1 connection for 100
13
+ # shards on the same server).
14
+ #
15
+ # This patch maintains the reference to the database during the cache clearing
16
+ # to ensure that the database doesn't get swapped out mid-way into an
17
+ # operation.
18
+ #
19
+ # This is a private Rails API and may change in future releases as they're
20
+ # actively working on sharding in Rails 6 and above.
21
+ module ClearQueryCachePatch
22
+ def clear_query_caches_for_current_thread
23
+ host_pool_current_database_was = connection.unproxied._host_pool_current_database
24
+ super
25
+ ensure
26
+ # restore in case clearing the cache changed the database
27
+ connection.unproxied._host_pool_current_database = host_pool_current_database_was
28
+ end
29
+ end
30
+ end
31
+
32
+ ActiveRecord::Base.singleton_class.prepend ActiveRecordHostPool::ClearQueryCachePatch
33
+ end
@@ -85,22 +85,21 @@ module ActiveRecordHostPool
85
85
  end
86
86
  end
87
87
 
88
+ # rubocop:disable Lint/DuplicateMethods
88
89
  module ActiveRecord
89
90
  module ConnectionAdapters
90
91
  class ConnectionHandler
91
- if ActiveRecord::VERSION::MAJOR == 5
92
- if ActiveRecord::VERSION::MINOR == 0
93
- raise "Unsupported version of Rails (v#{ActiveRecord::VERSION::STRING})"
94
- else
95
- def establish_connection(spec)
96
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
97
- spec = resolver.spec(spec)
98
-
99
- owner_to_pool[spec.name] = ActiveRecordHostPool::PoolProxy.new(spec)
100
- end
92
+ case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
93
+ when '5.1', '5.2', '6.0'
94
+
95
+ def establish_connection(spec)
96
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
97
+ spec = resolver.spec(spec)
98
+
99
+ owner_to_pool[spec.name] = ActiveRecordHostPool::PoolProxy.new(spec)
101
100
  end
102
101
 
103
- elsif ActiveRecord::VERSION::MAJOR == 4
102
+ when '4.2'
104
103
 
105
104
  def establish_connection(owner, spec)
106
105
  @class_to_pool.clear
@@ -116,5 +115,6 @@ module ActiveRecord
116
115
  end
117
116
  end
118
117
  end
118
+ # rubocop:enable Lint/DuplicateMethods
119
119
 
120
120
  ActiveRecord::ConnectionAdapters::Mysql2Adapter.include(ActiveRecordHostPool::DatabaseSwitch)
@@ -84,6 +84,34 @@ module ActiveRecordHostPool
84
84
  _clear_connection_proxy_cache
85
85
  end
86
86
 
87
+ def release_connection(*args)
88
+ p = _connection_pool(false)
89
+ return unless p
90
+
91
+ p.release_connection(*args)
92
+ end
93
+
94
+ def flush!
95
+ p = _connection_pool(false)
96
+ return unless p
97
+
98
+ p.flush!
99
+ end
100
+
101
+ def discard!
102
+ p = _connection_pool(false)
103
+ return unless p
104
+
105
+ p.discard!
106
+
107
+ # All connections in the pool (even if they're currently
108
+ # leased!) have just been discarded, along with the pool itself.
109
+ # Any further interaction with the pool (except #spec and #schema_cache)
110
+ # is undefined.
111
+ # Remove the connection for the given key so a new one can be created in its place
112
+ _connection_pools.delete(_pool_key)
113
+ end
114
+
87
115
  private
88
116
 
89
117
  def rescuable_errors
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordHostPool
4
- VERSION = "0.12.0"
4
+ VERSION = "1.0.3"
5
5
  end
data/test/database.yml CHANGED
@@ -66,3 +66,12 @@ test_host_1_db_not_there:
66
66
  password: <%= mysql.password %>
67
67
  host: <%= mysql.host %>
68
68
  reconnect: true
69
+
70
+ test_host_1_db_shard:
71
+ adapter: mysql2
72
+ encoding: utf8
73
+ database: arhp_test_1_shard
74
+ username: <%= mysql.user %>
75
+ password: <%= mysql.password %>
76
+ host: <%= mysql.host %>
77
+ reconnect: true
data/test/helper.rb CHANGED
@@ -25,6 +25,13 @@ module ARHPTestSetup
25
25
  return if ARHPTestSetup.const_defined?('Test1')
26
26
 
27
27
  eval <<-RUBY
28
+ # The placement of the Test1Shard class is important so that its
29
+ # connection will not be the most recent connection established
30
+ # for test_host_1.
31
+ class Test1Shard < ::ActiveRecord::Base
32
+ establish_connection(:test_host_1_db_shard)
33
+ end
34
+
28
35
  class Test1 < ActiveRecord::Base
29
36
  self.table_name = "tests"
30
37
  establish_connection(:test_host_1_db_1)
@@ -60,4 +67,15 @@ module ARHPTestSetup
60
67
  def current_database(klass)
61
68
  klass.connection.select_value('select DATABASE()')
62
69
  end
70
+
71
+ # Remove a method from a given module that fixes something.
72
+ # Execute the passed in block.
73
+ # Re-add the method back to the module.
74
+ def without_module_patch(mod, method_name)
75
+ method_body = mod.instance_method(method_name)
76
+ mod.remove_method(method_name)
77
+ yield if block_given?
78
+ ensure
79
+ mod.define_method(method_name, method_body)
80
+ end
63
81
  end
data/test/schema.rb CHANGED
@@ -2,7 +2,15 @@
2
2
 
3
3
  require_relative 'helper'
4
4
  ActiveRecord::Schema.define(version: 1) do
5
- create_table 'tests' do |t|
5
+ create_table 'tests', force: true do |t|
6
6
  t.string 'val'
7
7
  end
8
+
9
+ # Add a table only the shard database will have. Conditional
10
+ # exists since Phenix loads the schema file for every database.
11
+ if ActiveRecord::Base.connection.current_database == 'arhp_test_1_shard'
12
+ create_table 'test1_shards' do |t|
13
+ t.string 'name'
14
+ end
15
+ end
8
16
  end
data/test/test_arhp.rb CHANGED
@@ -13,6 +13,23 @@ class ActiveRecordHostPoolTest < Minitest::Test
13
13
  Phenix.burn!
14
14
  end
15
15
 
16
+ def test_process_forking_with_connections
17
+ # Ensure we have a connection already
18
+ assert_equal(true, ActiveRecord::Base.connected?)
19
+
20
+ # Verify that when we fork, the process doesn't crash
21
+ pid = Process.fork do
22
+ if ActiveRecord.version >= Gem::Version.new('5.2')
23
+ assert_equal(false, ActiveRecord::Base.connected?) # New to Rails 5.2
24
+ else
25
+ assert_equal(true, ActiveRecord::Base.connected?)
26
+ end
27
+ end
28
+ Process.wait(pid)
29
+ # Cleanup any connections we may have left around
30
+ ActiveRecord::Base.connection_handler.clear_all_connections!
31
+ end
32
+
16
33
  def test_models_with_matching_hosts_should_share_a_connection
17
34
  assert_equal(Test1.connection.raw_connection, Test2.connection.raw_connection)
18
35
  assert_equal(Test3.connection.raw_connection, Test4.connection.raw_connection)
@@ -38,6 +55,38 @@ class ActiveRecordHostPoolTest < Minitest::Test
38
55
  assert_action_uses_correct_database(:insert, "insert into tests values(NULL, 'foo')")
39
56
  end
40
57
 
58
+ def test_models_with_matching_hosts_and_non_matching_databases_should_share_a_connection
59
+ simulate_rails_app_active_record_railties
60
+ assert_equal(Test1.connection.raw_connection, Test1Shard.connection.raw_connection)
61
+ end
62
+
63
+ if ActiveRecord.version >= Gem::Version.new('6.0')
64
+ def test_models_with_matching_hosts_and_non_matching_databases_issue_exists_without_arhp_patch
65
+ simulate_rails_app_active_record_railties
66
+
67
+ # Remove patch that fixes an issue in Rails 6+ to ensure it still
68
+ # exists. If this begins to fail then it may mean that Rails has fixed
69
+ # the issue so that it no longer occurs.
70
+ without_module_patch(ActiveRecordHostPool::ClearQueryCachePatch, :clear_query_caches_for_current_thread) do
71
+ exception = assert_raises(ActiveRecord::StatementInvalid) do
72
+ ActiveRecord::Base.cache { Test1Shard.create! }
73
+ end
74
+
75
+ assert_equal("Mysql2::Error: Table 'arhp_test_2.test1_shards' doesn't exist", exception.message)
76
+ end
77
+ end
78
+
79
+ def test_models_with_matching_hosts_and_non_matching_databases_do_not_mix_up_underlying_database
80
+ simulate_rails_app_active_record_railties
81
+
82
+ # ActiveRecord 6.0 introduced a change that surfaced a problematic code
83
+ # path in active_record_host_pool when clearing caches across connection
84
+ # handlers which can cause the database to change.
85
+ # See ActiveRecordHostPool::ClearQueryCachePatch
86
+ ActiveRecord::Base.cache { Test1Shard.create! }
87
+ end
88
+ end
89
+
41
90
  def test_connection_returns_a_proxy
42
91
  assert_kind_of ActiveRecordHostPool::ConnectionProxy, Test1.connection
43
92
  end
@@ -146,6 +195,13 @@ class ActiveRecordHostPoolTest < Minitest::Test
146
195
  assert_equal expected_database, current_database(switch_to_klass)
147
196
  end
148
197
 
198
+ def test_release_connection
199
+ pool = ActiveRecord::Base.connection_pool
200
+ conn = pool.connection
201
+ pool.expects(:checkin).with(conn)
202
+ pool.release_connection
203
+ end
204
+
149
205
  private
150
206
 
151
207
  def assert_action_uses_correct_database(action, sql)
@@ -156,4 +212,16 @@ class ActiveRecordHostPoolTest < Minitest::Test
156
212
  assert_equal desired_db, current_database(klass)
157
213
  end
158
214
  end
215
+
216
+ def simulate_rails_app_active_record_railties
217
+ if ActiveRecord.version >= Gem::Version.new('6.0')
218
+ # Necessary for testing ActiveRecord 6.0 which uses the connection
219
+ # handlers when clearing query caches across all handlers when
220
+ # an operation that dirties the cache is involved (e.g. create/insert,
221
+ # update, delete/destroy, truncate, etc.)
222
+ ActiveRecord::Base.connection_handlers = {
223
+ ActiveRecord::Base.writing_role => ActiveRecord::Base.default_connection_handler
224
+ }
225
+ end
226
+ end
159
227
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_host_pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Quorning
8
8
  - Gabe Martin-Dempesy
9
9
  - Pierre Schambacher
10
10
  - Ben Osheroff
11
- autorequire:
11
+ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2019-08-21 00:00:00.000000000 Z
14
+ date: 2021-02-09 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activerecord
@@ -22,7 +22,7 @@ dependencies:
22
22
  version: 4.2.0
23
23
  - - "<"
24
24
  - !ruby/object:Gem::Version
25
- version: '6.0'
25
+ version: '6.1'
26
26
  type: :runtime
27
27
  prerelease: false
28
28
  version_requirements: !ruby/object:Gem::Requirement
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: 4.2.0
33
33
  - - "<"
34
34
  - !ruby/object:Gem::Version
35
- version: '6.0'
35
+ version: '6.1'
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: mysql2
38
38
  requirement: !ruby/object:Gem::Requirement
@@ -109,14 +109,14 @@ dependencies:
109
109
  requirements:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: 0.62.0
112
+ version: 0.80.0
113
113
  type: :development
114
114
  prerelease: false
115
115
  version_requirements: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - "~>"
118
118
  - !ruby/object:Gem::Version
119
- version: 0.62.0
119
+ version: 0.80.0
120
120
  - !ruby/object:Gem::Dependency
121
121
  name: shoulda
122
122
  requirement: !ruby/object:Gem::Requirement
@@ -145,6 +145,7 @@ files:
145
145
  - MIT-LICENSE
146
146
  - Readme.md
147
147
  - lib/active_record_host_pool.rb
148
+ - lib/active_record_host_pool/clear_query_cache_patch.rb
148
149
  - lib/active_record_host_pool/connection_adapter_mixin.rb
149
150
  - lib/active_record_host_pool/connection_proxy.rb
150
151
  - lib/active_record_host_pool/pool_proxy.rb
@@ -157,7 +158,7 @@ homepage: https://github.com/zendesk/active_record_host_pool
157
158
  licenses:
158
159
  - MIT
159
160
  metadata: {}
160
- post_install_message:
161
+ post_install_message:
161
162
  rdoc_options: []
162
163
  require_paths:
163
164
  - lib
@@ -172,8 +173,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
173
  - !ruby/object:Gem::Version
173
174
  version: '0'
174
175
  requirements: []
175
- rubygems_version: 3.0.3
176
- signing_key:
176
+ rubygems_version: 3.2.2
177
+ signing_key:
177
178
  specification_version: 4
178
179
  summary: Allow ActiveRecord to share a connection to multiple databases on the same
179
180
  host