active_record_host_pool 1.0.2 → 1.1.1
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 +4 -4
- data/Readme.md +36 -0
- data/lib/active_record_host_pool/clear_query_cache_patch.rb +42 -0
- data/lib/active_record_host_pool/connection_adapter_mixin.rb +19 -12
- data/lib/active_record_host_pool/pool_proxy.rb +4 -155
- data/lib/active_record_host_pool/pool_proxy_6_1.rb +150 -0
- data/lib/active_record_host_pool/pool_proxy_legacy.rb +152 -0
- data/lib/active_record_host_pool/version.rb +1 -1
- data/test/database.yml +52 -21
- data/test/helper.rb +157 -43
- data/test/schema.rb +16 -9
- data/test/test_arhp.rb +56 -103
- metadata +29 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dc3e41230c1cc7b208056d7f6fe99abe717e190b55cdca3ff5df9f1513fcad1
|
4
|
+
data.tar.gz: 92f1982d46da0644184083a78d72ad878d54ff70413f347fcb67bc3890cfaeca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3e4c62f5aee3ae968622ca9a7fef59fd9974c4153c244c312715036f6753a0ec58ba3083247bb9a4b9479db3a3c55a8182c85ae320d273ad90a1084ea745b46
|
7
|
+
data.tar.gz: 1fcec2e7e23272e50894a19bd4fc195e0360089d3a5e44f98fc6a589723f3a6bb8dae2276dbeeec31f05f87f89641308909412b28f73f929bc2358605fe30937
|
data/Readme.md
CHANGED
@@ -5,6 +5,42 @@
|
|
5
5
|
This gem allows for one ActiveRecord connection to be used to connect to multiple databases on a server.
|
6
6
|
It accomplishes this by calling select_db() as necessary to switch databases between database calls.
|
7
7
|
|
8
|
+
## How Connections Are Pooled
|
9
|
+
|
10
|
+
ARHP creates separate connection pools based on the pool key.
|
11
|
+
|
12
|
+
The pool key is defined as:
|
13
|
+
|
14
|
+
`host / port / socket / username / replica`
|
15
|
+
|
16
|
+
Therefore two databases with identical host, port, socket, username, and replica status will share a connection pool.
|
17
|
+
If any part (host, port, etc.) of the pool key differ, two databases will _not_ share a connection pool.
|
18
|
+
|
19
|
+
`replica` in the pool key is a boolean indicating if the database is a replica/reader (true) or writer database (false).
|
20
|
+
|
21
|
+
Below, `test_pool_1` and `test_pool_2` have identical host, username, socket, and replica status but the port information differs.
|
22
|
+
Here the database configurations are formatted as a table to give a visual example:
|
23
|
+
|
24
|
+
| | test_pool_1 | test_pool_2 |
|
25
|
+
|----------|----------------|----------------|
|
26
|
+
| host | 127.0.0.1 | 127.0.0.1 |
|
27
|
+
| port | | 3306 |
|
28
|
+
| socket | | |
|
29
|
+
| username | root | root |
|
30
|
+
| replica | false | false |
|
31
|
+
|
32
|
+
The configuration items must be explicitly defined or they will be blank in the pool key.
|
33
|
+
Configurations with matching _implicit_ items but differing _explicit_ items will create separate pools.
|
34
|
+
e.g. `test_pool_1` will default to port 3306 but because it is not explicitly defined it will not share a pool with `test_pool_2`
|
35
|
+
|
36
|
+
ARHP will therefore create the following pool keys:
|
37
|
+
|
38
|
+
```
|
39
|
+
test_pool_1 => 127.0.0.1///root/false
|
40
|
+
test_pool_2 => 127.0.0.1/3306//root/false
|
41
|
+
```
|
42
|
+
|
43
|
+
|
8
44
|
## Support
|
9
45
|
|
10
46
|
For now, the only backend known to work is MySQL, with the mysql2 gem.
|
@@ -0,0 +1,42 @@
|
|
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
|
+
|
30
|
+
def clear_on_handler(handler)
|
31
|
+
handler.all_connection_pools.each do |pool|
|
32
|
+
db_was = pool.connection.unproxied._host_pool_current_database
|
33
|
+
pool.connection.clear_query_cache if pool.active_connection?
|
34
|
+
ensure
|
35
|
+
pool.connection.unproxied._host_pool_current_database = db_was
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ActiveRecord::Base.singleton_class.prepend ActiveRecordHostPool::ClearQueryCachePatch
|
42
|
+
end
|
@@ -10,7 +10,7 @@ module ActiveRecordHostPool
|
|
10
10
|
|
11
11
|
def _host_pool_current_database=(database)
|
12
12
|
@_host_pool_current_database = database
|
13
|
-
@config[:database] = _host_pool_current_database
|
13
|
+
@config[:database] = _host_pool_current_database
|
14
14
|
end
|
15
15
|
|
16
16
|
alias_method :execute_without_switching, :execute
|
@@ -83,13 +83,24 @@ module ActiveRecordHostPool
|
|
83
83
|
super(_host_pool_current_database.to_s + "/" + sql, *args)
|
84
84
|
end
|
85
85
|
end
|
86
|
+
|
87
|
+
module PoolConfigPatch
|
88
|
+
def pool
|
89
|
+
ActiveSupport::ForkTracker.check!
|
90
|
+
|
91
|
+
@pool || synchronize { @pool ||= ActiveRecordHostPool::PoolProxy.new(self) }
|
92
|
+
end
|
93
|
+
end
|
86
94
|
end
|
87
95
|
|
88
|
-
# rubocop:disable Lint/DuplicateMethods
|
89
96
|
module ActiveRecord
|
90
97
|
module ConnectionAdapters
|
91
98
|
class ConnectionHandler
|
92
99
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
100
|
+
when '6.1'
|
101
|
+
|
102
|
+
:noop
|
103
|
+
|
93
104
|
when '5.1', '5.2', '6.0'
|
94
105
|
|
95
106
|
def establish_connection(spec)
|
@@ -99,15 +110,6 @@ module ActiveRecord
|
|
99
110
|
owner_to_pool[spec.name] = ActiveRecordHostPool::PoolProxy.new(spec)
|
100
111
|
end
|
101
112
|
|
102
|
-
when '4.2'
|
103
|
-
|
104
|
-
def establish_connection(owner, spec)
|
105
|
-
@class_to_pool.clear
|
106
|
-
raise "Anonymous class is not allowed." unless owner.name
|
107
|
-
|
108
|
-
owner_to_pool[owner.name] = ActiveRecordHostPool::PoolProxy.new(spec)
|
109
|
-
end
|
110
|
-
|
111
113
|
else
|
112
114
|
|
113
115
|
raise "Unsupported version of Rails (v#{ActiveRecord::VERSION::STRING})"
|
@@ -115,6 +117,11 @@ module ActiveRecord
|
|
115
117
|
end
|
116
118
|
end
|
117
119
|
end
|
118
|
-
# rubocop:enable Lint/DuplicateMethods
|
119
120
|
|
120
121
|
ActiveRecord::ConnectionAdapters::Mysql2Adapter.include(ActiveRecordHostPool::DatabaseSwitch)
|
122
|
+
|
123
|
+
# In Rails 6.1 Connection Pools are no longer instantiated in #establish_connection but in a
|
124
|
+
# new pool method.
|
125
|
+
if "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}" == '6.1'
|
126
|
+
ActiveRecord::ConnectionAdapters::PoolConfig.prepend(ActiveRecordHostPool::PoolConfigPatch)
|
127
|
+
end
|
@@ -1,158 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require '
|
5
|
-
|
6
|
-
|
7
|
-
# this module sits in between ConnectionHandler and a bunch of different ConnectionPools (one per host).
|
8
|
-
# when a connection is requested, it goes like:
|
9
|
-
# ActiveRecordClass -> ConnectionHandler#connection
|
10
|
-
# ConnectionHandler#connection -> (find or create PoolProxy)
|
11
|
-
# PoolProxy -> shared list of Pools
|
12
|
-
# Pool actually gives back a connection, then PoolProxy turns this
|
13
|
-
# into a ConnectionProxy that can inform (on execute) which db we should be on.
|
14
|
-
|
15
|
-
module ActiveRecordHostPool
|
16
|
-
class PoolProxy < Delegator
|
17
|
-
def initialize(spec)
|
18
|
-
super(spec)
|
19
|
-
@spec = spec
|
20
|
-
@config = spec.config
|
21
|
-
end
|
22
|
-
|
23
|
-
def __getobj__
|
24
|
-
_connection_pool
|
25
|
-
end
|
26
|
-
|
27
|
-
def __setobj__(spec)
|
28
|
-
@spec = spec
|
29
|
-
@config = spec.config
|
30
|
-
@_pool_key = nil
|
31
|
-
end
|
32
|
-
|
33
|
-
def spec
|
34
|
-
@spec
|
35
|
-
end
|
36
|
-
|
37
|
-
def connection(*args)
|
38
|
-
real_connection = _connection_pool.connection(*args)
|
39
|
-
_connection_proxy_for(real_connection, @config[:database])
|
40
|
-
rescue Exception => e
|
41
|
-
if rescuable_errors.any? { |r| e.is_a?(r) }
|
42
|
-
_connection_pools.delete(_pool_key)
|
43
|
-
end
|
44
|
-
Kernel.raise(e)
|
45
|
-
end
|
46
|
-
|
47
|
-
# by the time we are patched into ActiveRecord, the current thread has already established
|
48
|
-
# a connection. thus we need to patch both connection and checkout/checkin
|
49
|
-
def checkout(*args, &block)
|
50
|
-
cx = _connection_pool.checkout(*args, &block)
|
51
|
-
_connection_proxy_for(cx, @config[:database])
|
52
|
-
end
|
53
|
-
|
54
|
-
def checkin(cx)
|
55
|
-
cx = cx.unproxied
|
56
|
-
_connection_pool.checkin(cx)
|
57
|
-
end
|
58
|
-
|
59
|
-
def with_connection
|
60
|
-
cx = checkout
|
61
|
-
yield cx
|
62
|
-
ensure
|
63
|
-
checkin cx
|
64
|
-
end
|
65
|
-
|
66
|
-
def disconnect!
|
67
|
-
p = _connection_pool(false)
|
68
|
-
return unless p
|
69
|
-
|
70
|
-
p.disconnect!
|
71
|
-
p.automatic_reconnect = true if p.respond_to?(:automatic_reconnect=)
|
72
|
-
_clear_connection_proxy_cache
|
73
|
-
end
|
74
|
-
|
75
|
-
def automatic_reconnect=(value)
|
76
|
-
p = _connection_pool(false)
|
77
|
-
return unless p
|
78
|
-
|
79
|
-
p.automatic_reconnect = value if p.respond_to?(:automatic_reconnect=)
|
80
|
-
end
|
81
|
-
|
82
|
-
def clear_reloadable_connections!
|
83
|
-
_connection_pool.clear_reloadable_connections!
|
84
|
-
_clear_connection_proxy_cache
|
85
|
-
end
|
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
|
-
|
115
|
-
private
|
116
|
-
|
117
|
-
def rescuable_errors
|
118
|
-
@rescuable_errors ||= begin
|
119
|
-
e = [Mysql2::Error]
|
120
|
-
if ActiveRecord.const_defined?("NoDatabaseError")
|
121
|
-
e << ActiveRecord::NoDatabaseError
|
122
|
-
end
|
123
|
-
e
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def _connection_pools
|
128
|
-
@@_connection_pools ||= {}
|
129
|
-
end
|
130
|
-
|
131
|
-
def _pool_key
|
132
|
-
@_pool_key ||= "#{@config[:host]}/#{@config[:port]}/#{@config[:socket]}/#{@config[:username]}/#{@config[:slave] && 'slave'}"
|
133
|
-
end
|
134
|
-
|
135
|
-
def _connection_pool(auto_create = true)
|
136
|
-
pool = _connection_pools[_pool_key]
|
137
|
-
if pool.nil? && auto_create
|
138
|
-
pool = _connection_pools[_pool_key] = ActiveRecord::ConnectionAdapters::ConnectionPool.new(@spec)
|
139
|
-
end
|
140
|
-
pool
|
141
|
-
end
|
142
|
-
|
143
|
-
def _connection_proxy_for(connection, database)
|
144
|
-
@connection_proxy_cache ||= {}
|
145
|
-
key = [connection, database]
|
146
|
-
|
147
|
-
@connection_proxy_cache[key] ||= begin
|
148
|
-
cx = ActiveRecordHostPool::ConnectionProxy.new(connection, database)
|
149
|
-
cx.execute('select 1')
|
150
|
-
cx
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def _clear_connection_proxy_cache
|
155
|
-
@connection_proxy_cache = {}
|
156
|
-
end
|
157
|
-
end
|
3
|
+
if "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}" == '6.1'
|
4
|
+
require 'active_record_host_pool/pool_proxy_6_1'
|
5
|
+
else
|
6
|
+
require 'active_record_host_pool/pool_proxy_legacy'
|
158
7
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'delegate'
|
4
|
+
require 'active_record'
|
5
|
+
require 'active_record_host_pool/connection_adapter_mixin'
|
6
|
+
|
7
|
+
# this module sits in between ConnectionHandler and a bunch of different ConnectionPools (one per host).
|
8
|
+
# when a connection is requested, it goes like:
|
9
|
+
# ActiveRecordClass -> ConnectionHandler#connection
|
10
|
+
# ConnectionHandler#connection -> (find or create PoolProxy)
|
11
|
+
# PoolProxy -> shared list of Pools
|
12
|
+
# Pool actually gives back a connection, then PoolProxy turns this
|
13
|
+
# into a ConnectionProxy that can inform (on execute) which db we should be on.
|
14
|
+
|
15
|
+
module ActiveRecordHostPool
|
16
|
+
# Sits between ConnectionHandler and a bunch of different ConnectionPools (one per host).
|
17
|
+
class PoolProxy < Delegator
|
18
|
+
def initialize(pool_config)
|
19
|
+
super(pool_config)
|
20
|
+
@pool_config = pool_config
|
21
|
+
@config = pool_config.db_config.configuration_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def __getobj__
|
25
|
+
_connection_pool
|
26
|
+
end
|
27
|
+
|
28
|
+
def __setobj__(pool_config)
|
29
|
+
@pool_config = pool_config
|
30
|
+
@config = pool_config.db_config.configuration_hash
|
31
|
+
@_pool_key = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :pool_config
|
35
|
+
|
36
|
+
def connection(*args)
|
37
|
+
real_connection = _connection_pool.connection(*args)
|
38
|
+
_connection_proxy_for(real_connection, @config[:database])
|
39
|
+
rescue Mysql2::Error, ActiveRecord::NoDatabaseError
|
40
|
+
_connection_pools.delete(_pool_key)
|
41
|
+
Kernel.raise
|
42
|
+
end
|
43
|
+
|
44
|
+
# by the time we are patched into ActiveRecord, the current thread has already established
|
45
|
+
# a connection. thus we need to patch both connection and checkout/checkin
|
46
|
+
def checkout(*args, &block)
|
47
|
+
cx = _connection_pool.checkout(*args, &block)
|
48
|
+
_connection_proxy_for(cx, @config[:database])
|
49
|
+
end
|
50
|
+
|
51
|
+
def checkin(cx)
|
52
|
+
cx = cx.unproxied
|
53
|
+
_connection_pool.checkin(cx)
|
54
|
+
end
|
55
|
+
|
56
|
+
def with_connection
|
57
|
+
cx = checkout
|
58
|
+
yield cx
|
59
|
+
ensure
|
60
|
+
checkin cx
|
61
|
+
end
|
62
|
+
|
63
|
+
def disconnect!
|
64
|
+
p = _connection_pool(false)
|
65
|
+
return unless p
|
66
|
+
|
67
|
+
p.disconnect!
|
68
|
+
p.automatic_reconnect = true if p.respond_to?(:automatic_reconnect=)
|
69
|
+
_clear_connection_proxy_cache
|
70
|
+
end
|
71
|
+
|
72
|
+
def automatic_reconnect=(value)
|
73
|
+
p = _connection_pool(false)
|
74
|
+
return unless p
|
75
|
+
|
76
|
+
p.automatic_reconnect = value if p.respond_to?(:automatic_reconnect=)
|
77
|
+
end
|
78
|
+
|
79
|
+
def clear_reloadable_connections!
|
80
|
+
_connection_pool.clear_reloadable_connections!
|
81
|
+
_clear_connection_proxy_cache
|
82
|
+
end
|
83
|
+
|
84
|
+
def release_connection(*args)
|
85
|
+
p = _connection_pool(false)
|
86
|
+
return unless p
|
87
|
+
|
88
|
+
p.release_connection(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def flush!
|
92
|
+
p = _connection_pool(false)
|
93
|
+
return unless p
|
94
|
+
|
95
|
+
p.flush!
|
96
|
+
end
|
97
|
+
|
98
|
+
def discard!
|
99
|
+
p = _connection_pool(false)
|
100
|
+
return unless p
|
101
|
+
|
102
|
+
p.discard!
|
103
|
+
|
104
|
+
# All connections in the pool (even if they're currently
|
105
|
+
# leased!) have just been discarded, along with the pool itself.
|
106
|
+
# Any further interaction with the pool (except #pool_config and #schema_cache)
|
107
|
+
# is undefined.
|
108
|
+
# Remove the connection for the given key so a new one can be created in its place
|
109
|
+
_connection_pools.delete(_pool_key)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def _connection_pools
|
115
|
+
@@_connection_pools ||= {}
|
116
|
+
end
|
117
|
+
|
118
|
+
def _pool_key
|
119
|
+
@_pool_key ||= "#{@config[:host]}/#{@config[:port]}/#{@config[:socket]}/" \
|
120
|
+
"#{@config[:username]}/#{replica_configuration? && 'replica'}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def _connection_pool(auto_create = true)
|
124
|
+
pool = _connection_pools[_pool_key]
|
125
|
+
if pool.nil? && auto_create
|
126
|
+
pool = _connection_pools[_pool_key] = ActiveRecord::ConnectionAdapters::ConnectionPool.new(@pool_config)
|
127
|
+
end
|
128
|
+
pool
|
129
|
+
end
|
130
|
+
|
131
|
+
def _connection_proxy_for(connection, database)
|
132
|
+
@connection_proxy_cache ||= {}
|
133
|
+
key = [connection, database]
|
134
|
+
|
135
|
+
@connection_proxy_cache[key] ||= begin
|
136
|
+
cx = ActiveRecordHostPool::ConnectionProxy.new(connection, database)
|
137
|
+
cx.execute('select 1')
|
138
|
+
cx
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def _clear_connection_proxy_cache
|
143
|
+
@connection_proxy_cache = {}
|
144
|
+
end
|
145
|
+
|
146
|
+
def replica_configuration?
|
147
|
+
@config[:replica] || @config[:slave]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# For versions of Rails < 6.1
|
4
|
+
|
5
|
+
require 'delegate'
|
6
|
+
require 'active_record'
|
7
|
+
require 'active_record_host_pool/connection_adapter_mixin'
|
8
|
+
|
9
|
+
# this module sits in between ConnectionHandler and a bunch of different ConnectionPools (one per host).
|
10
|
+
# when a connection is requested, it goes like:
|
11
|
+
# ActiveRecordClass -> ConnectionHandler#connection
|
12
|
+
# ConnectionHandler#connection -> (find or create PoolProxy)
|
13
|
+
# PoolProxy -> shared list of Pools
|
14
|
+
# Pool actually gives back a connection, then PoolProxy turns this
|
15
|
+
# into a ConnectionProxy that can inform (on execute) which db we should be on.
|
16
|
+
|
17
|
+
module ActiveRecordHostPool
|
18
|
+
# Sits between ConnectionHandler and a bunch of different ConnectionPools (one per host).
|
19
|
+
class PoolProxy < Delegator
|
20
|
+
def initialize(spec)
|
21
|
+
super(spec)
|
22
|
+
@spec = spec
|
23
|
+
@config = spec.config
|
24
|
+
end
|
25
|
+
|
26
|
+
def __getobj__
|
27
|
+
_connection_pool
|
28
|
+
end
|
29
|
+
|
30
|
+
def __setobj__(spec)
|
31
|
+
@spec = spec
|
32
|
+
@config = spec.config
|
33
|
+
@_pool_key = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :spec
|
37
|
+
|
38
|
+
def connection(*args)
|
39
|
+
real_connection = _connection_pool.connection(*args)
|
40
|
+
_connection_proxy_for(real_connection, @config[:database])
|
41
|
+
rescue Mysql2::Error, ActiveRecord::NoDatabaseError
|
42
|
+
_connection_pools.delete(_pool_key)
|
43
|
+
Kernel.raise
|
44
|
+
end
|
45
|
+
|
46
|
+
# by the time we are patched into ActiveRecord, the current thread has already established
|
47
|
+
# a connection. thus we need to patch both connection and checkout/checkin
|
48
|
+
def checkout(*args, &block)
|
49
|
+
cx = _connection_pool.checkout(*args, &block)
|
50
|
+
_connection_proxy_for(cx, @config[:database])
|
51
|
+
end
|
52
|
+
|
53
|
+
def checkin(cx)
|
54
|
+
cx = cx.unproxied
|
55
|
+
_connection_pool.checkin(cx)
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_connection
|
59
|
+
cx = checkout
|
60
|
+
yield cx
|
61
|
+
ensure
|
62
|
+
checkin cx
|
63
|
+
end
|
64
|
+
|
65
|
+
def disconnect!
|
66
|
+
p = _connection_pool(false)
|
67
|
+
return unless p
|
68
|
+
|
69
|
+
p.disconnect!
|
70
|
+
p.automatic_reconnect = true if p.respond_to?(:automatic_reconnect=)
|
71
|
+
_clear_connection_proxy_cache
|
72
|
+
end
|
73
|
+
|
74
|
+
def automatic_reconnect=(value)
|
75
|
+
p = _connection_pool(false)
|
76
|
+
return unless p
|
77
|
+
|
78
|
+
p.automatic_reconnect = value if p.respond_to?(:automatic_reconnect=)
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear_reloadable_connections!
|
82
|
+
_connection_pool.clear_reloadable_connections!
|
83
|
+
_clear_connection_proxy_cache
|
84
|
+
end
|
85
|
+
|
86
|
+
def release_connection(*args)
|
87
|
+
p = _connection_pool(false)
|
88
|
+
return unless p
|
89
|
+
|
90
|
+
p.release_connection(*args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def flush!
|
94
|
+
p = _connection_pool(false)
|
95
|
+
return unless p
|
96
|
+
|
97
|
+
p.flush!
|
98
|
+
end
|
99
|
+
|
100
|
+
def discard!
|
101
|
+
p = _connection_pool(false)
|
102
|
+
return unless p
|
103
|
+
|
104
|
+
p.discard!
|
105
|
+
|
106
|
+
# All connections in the pool (even if they're currently
|
107
|
+
# leased!) have just been discarded, along with the pool itself.
|
108
|
+
# Any further interaction with the pool (except #spec and #schema_cache)
|
109
|
+
# is undefined.
|
110
|
+
# Remove the connection for the given key so a new one can be created in its place
|
111
|
+
_connection_pools.delete(_pool_key)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def _connection_pools
|
117
|
+
@@_connection_pools ||= {}
|
118
|
+
end
|
119
|
+
|
120
|
+
def _pool_key
|
121
|
+
@_pool_key ||= "#{@config[:host]}/#{@config[:port]}/#{@config[:socket]}/" \
|
122
|
+
"#{@config[:username]}/#{replica_configuration? && 'replica'}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def _connection_pool(auto_create = true)
|
126
|
+
pool = _connection_pools[_pool_key]
|
127
|
+
if pool.nil? && auto_create
|
128
|
+
pool = _connection_pools[_pool_key] = ActiveRecord::ConnectionAdapters::ConnectionPool.new(@spec)
|
129
|
+
end
|
130
|
+
pool
|
131
|
+
end
|
132
|
+
|
133
|
+
def _connection_proxy_for(connection, database)
|
134
|
+
@connection_proxy_cache ||= {}
|
135
|
+
key = [connection, database]
|
136
|
+
|
137
|
+
@connection_proxy_cache[key] ||= begin
|
138
|
+
cx = ActiveRecordHostPool::ConnectionProxy.new(connection, database)
|
139
|
+
cx.execute('select 1')
|
140
|
+
cx
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def _clear_connection_proxy_cache
|
145
|
+
@connection_proxy_cache = {}
|
146
|
+
end
|
147
|
+
|
148
|
+
def replica_configuration?
|
149
|
+
@config[:replica] || @config[:slave]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|