active_record_host_pool 0.12.0 → 1.0.3

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: 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