active_replicas 0.3.2 → 0.4.0
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/gemfiles/rails_4.gemfile.lock +1 -1
- data/lib/active_replicas/proxying_connection_pool.rb +32 -57
- data/lib/active_replicas/rails4/connection_handler.rb +21 -59
- data/lib/active_replicas/rails4/helpers.rb +21 -0
- data/lib/active_replicas/rails4/process_local_connection_handler.rb +119 -0
- data/lib/active_replicas/version.rb +1 -1
- data/lib/active_replicas.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e56f8bd047e2f96b83a8246b4cdbcd428dd4e095
|
4
|
+
data.tar.gz: e02a7e2680c7bc60aa47c2cd75a25ca704f3a5c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f430544ad26fd4f894addafb5961ea1cfc5aabf99fc565a206556bf70d302efe7afc4b4ae4447345c95c4ec1793d95d109400aa1995b7e72bfe4c1c644d2276e
|
7
|
+
data.tar.gz: 755fc9c0a43f3860d4c8f2f5b7ef984740f401a51b10dc50c29f0da1bf964648df7d813457272529ab9da73177ce4e2a94db7e64b9e03d622fa305b0e8c57f66
|
@@ -1,5 +1,6 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
2
|
require 'active_support/hash_with_indifferent_access'
|
3
|
+
require 'monitor'
|
3
4
|
|
4
5
|
module ActiveReplicas
|
5
6
|
# Manages connection pools to the primary and replica databases. Returns
|
@@ -7,35 +8,23 @@ module ActiveReplicas
|
|
7
8
|
#
|
8
9
|
# Also hanldes the internal state of switching back and forth from replica
|
9
10
|
# to primary connections based on heuristics or overrides.
|
11
|
+
#
|
12
|
+
# NOTE: This proxy instance should be provisioned per-thread and it is *not*
|
13
|
+
# thread-safe!
|
10
14
|
class ProxyingConnectionPool
|
11
|
-
attr_reader :
|
15
|
+
attr_reader :handler
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@replica_pools = (proxy_configuration[:replicas] || {}).map do |name, config_spec|
|
17
|
-
[ name, ProxyingConnectionPool.connection_pool_for_spec(config_spec) ]
|
18
|
-
end.to_h
|
17
|
+
# handler - `ProcessLocalConnectionHandler` which created this pool.
|
18
|
+
def initialize(handler)
|
19
|
+
@handler = handler
|
19
20
|
|
20
21
|
# Calls to `with_primary` will increment and decrement this.
|
21
22
|
@primary_depth = 0
|
22
23
|
# Current connection pool.
|
23
24
|
@current_pool = nil
|
24
|
-
|
25
|
-
extend MonitorMixin
|
26
25
|
end
|
27
26
|
|
28
|
-
|
29
|
-
# configured with the given specification.
|
30
|
-
def self.connection_pool_for_spec(config_spec)
|
31
|
-
@@resolver ||= ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
|
32
|
-
|
33
|
-
# Turns a hash configuration into a `ConnectionSpecification` that can
|
34
|
-
# be passed to a `ConnectionPool`.
|
35
|
-
spec = @@resolver.spec ActiveSupport::HashWithIndifferentAccess.new(config_spec)
|
36
|
-
|
37
|
-
ActiveRecord::ConnectionAdapters::ConnectionPool.new spec
|
38
|
-
end
|
27
|
+
delegate :primary_pool, :replica_pools, to: :handler
|
39
28
|
|
40
29
|
# ConnectionPool interface methods
|
41
30
|
# ================================
|
@@ -47,23 +36,19 @@ module ActiveReplicas
|
|
47
36
|
return unless conn
|
48
37
|
|
49
38
|
ProxyingConnection.new connection: conn,
|
50
|
-
is_primary: pool ==
|
39
|
+
is_primary: pool == primary_pool,
|
51
40
|
proxy: self
|
52
41
|
end
|
53
42
|
|
54
43
|
def active_connection?
|
55
|
-
|
56
|
-
all_pools.any? { |pool| pool.active_connection? }
|
57
|
-
end
|
44
|
+
all_pools.any?(&:active_connection?)
|
58
45
|
end
|
59
46
|
|
60
47
|
def release_connection
|
61
|
-
|
62
|
-
each_pool &:release_connection
|
48
|
+
all_pools.each(&:release_connection)
|
63
49
|
|
64
|
-
|
65
|
-
|
66
|
-
end
|
50
|
+
@primary_depth = 0
|
51
|
+
@current_pool = nil
|
67
52
|
end
|
68
53
|
|
69
54
|
def with_connection(&block)
|
@@ -71,21 +56,15 @@ module ActiveReplicas
|
|
71
56
|
end
|
72
57
|
|
73
58
|
def connected?
|
74
|
-
|
75
|
-
current_pool.connected?
|
76
|
-
end
|
59
|
+
current_pool.connected?
|
77
60
|
end
|
78
61
|
|
79
62
|
def disconnect!
|
80
|
-
|
81
|
-
each_pool &:disconnect!
|
82
|
-
end
|
63
|
+
all_pools.each(&:disconnect!)
|
83
64
|
end
|
84
65
|
|
85
66
|
def clear_reloadable_connections!
|
86
|
-
|
87
|
-
each_pool &:clear_reloadable_connections!
|
88
|
-
end
|
67
|
+
all_pools.each(&:clear_reloadable_connections!)
|
89
68
|
end
|
90
69
|
|
91
70
|
def current_pool
|
@@ -100,17 +79,17 @@ module ActiveReplicas
|
|
100
79
|
# ==============================================
|
101
80
|
|
102
81
|
def automatic_reconnect=(new_value)
|
103
|
-
|
82
|
+
all_pools.each do |pool|
|
104
83
|
pool.automatic_reconnect = new_value
|
105
84
|
end
|
106
85
|
end
|
107
86
|
|
108
87
|
def connections
|
109
|
-
|
88
|
+
primary_pool.connections
|
110
89
|
end
|
111
90
|
|
112
91
|
def spec
|
113
|
-
|
92
|
+
primary_pool.spec
|
114
93
|
end
|
115
94
|
|
116
95
|
# Additional methods
|
@@ -120,7 +99,7 @@ module ActiveReplicas
|
|
120
99
|
previous_pool = @current_pool
|
121
100
|
|
122
101
|
@primary_depth += 1
|
123
|
-
@current_pool =
|
102
|
+
@current_pool = primary_pool
|
124
103
|
|
125
104
|
yield connection
|
126
105
|
ensure
|
@@ -136,31 +115,27 @@ module ActiveReplicas
|
|
136
115
|
#
|
137
116
|
# NOTE: If this is not already in a `with_primary` block then calling this
|
138
117
|
# will irreversably place the proxying pool in the primary state until
|
139
|
-
# `
|
118
|
+
# `release_connection!` is called! If you want to *temporarily*
|
140
119
|
# use the primary then explicitly do so using `with_primary`.
|
141
120
|
def primary_connection
|
142
121
|
if @primary_depth == 0
|
143
122
|
@primary_depth += 1
|
144
|
-
@current_pool =
|
123
|
+
@current_pool = primary_pool
|
145
124
|
end
|
146
125
|
|
147
126
|
connection
|
148
127
|
end
|
149
128
|
|
150
|
-
# Returns an `Enumerable` over all the pools, primary and replicas,
|
129
|
+
# Returns an `Enumerable` over all the pools, primary and replicas, used
|
151
130
|
# by this proxying pool.
|
152
131
|
def all_pools
|
153
|
-
[
|
154
|
-
end
|
155
|
-
|
156
|
-
def each_pool(&block)
|
157
|
-
all_pools.each &block
|
132
|
+
[primary_pool] + replica_pools.values
|
158
133
|
end
|
159
134
|
|
160
135
|
def pool_which_owns_connection(object_id)
|
161
|
-
return
|
136
|
+
return primary_pool if primary_pool.connections.any? { |c| c.object_id == object_id }
|
162
137
|
|
163
|
-
|
138
|
+
replica_pools.values.each do |pool|
|
164
139
|
return pool if pool.connections.any? { |c| c.object_id == object_id }
|
165
140
|
end
|
166
141
|
|
@@ -168,20 +143,20 @@ module ActiveReplicas
|
|
168
143
|
end
|
169
144
|
|
170
145
|
def primary_pool?(pool)
|
171
|
-
pool ==
|
146
|
+
pool == primary_pool
|
172
147
|
end
|
173
148
|
|
174
149
|
def replica_pool?(pool)
|
175
|
-
|
150
|
+
replica_pools.values.include? pool
|
176
151
|
end
|
177
152
|
|
178
153
|
private
|
179
154
|
|
180
155
|
def next_pool
|
181
|
-
replicas =
|
156
|
+
replicas = replica_pools.values
|
182
157
|
|
183
158
|
if replicas.empty?
|
184
|
-
|
159
|
+
primary_pool
|
185
160
|
else
|
186
161
|
replicas.sample
|
187
162
|
end
|
@@ -1,9 +1,15 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
1
2
|
require 'concurrent/map'
|
2
3
|
|
4
|
+
require 'active_replicas/rails4/process_local_connection_handler'
|
5
|
+
|
3
6
|
module ActiveReplicas
|
4
7
|
module Rails4
|
5
8
|
# Wraps around Rails' `ActiveRecord::ConnectionAdapters::ConnectionHandler`
|
6
9
|
# to provide proxy wrappers around requested connections.
|
10
|
+
#
|
11
|
+
# This is the process-safe handler; it creates instances of
|
12
|
+
# `ThreadLocalConnectionHandler` to provide proxy wrappers for each thread.
|
7
13
|
class ConnectionHandler
|
8
14
|
attr_accessor :proxy_configuration
|
9
15
|
|
@@ -12,72 +18,28 @@ module ActiveReplicas
|
|
12
18
|
# @delegate = delegate
|
13
19
|
# @overrides = Set.new(overrides || [])
|
14
20
|
|
15
|
-
# Each process will get its own
|
16
|
-
|
17
|
-
@process_to_connection_pool = Concurrent::Map.new
|
18
|
-
end
|
19
|
-
|
20
|
-
def connection_pool_list
|
21
|
-
[ @process_to_connection_pool[Process.pid] ].compact
|
22
|
-
end
|
23
|
-
|
24
|
-
def establish_connection(owner, spec)
|
25
|
-
prefix = '[ActiveReplicas::Rails4::ConnectionHandler#establish_connection]'
|
26
|
-
ActiveRecord::Base.logger&.warn "#{prefix} Ignoring spec for #{owner.inspect}: #{spec.inspect}"
|
27
|
-
ActiveRecord::Base.logger&.info "#{prefix} Called from:\n" + Kernel.caller.first(5).map {|t| " #{t}" }.join("\n")
|
28
|
-
|
29
|
-
proxying_connection_pool
|
21
|
+
# Each process will get its own thread-safe handler.
|
22
|
+
@process_to_handler = Concurrent::Map.new
|
30
23
|
end
|
31
24
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
proxying_connection_pool.release_connection
|
38
|
-
end
|
39
|
-
|
40
|
-
def clear_reloadable_connections!
|
41
|
-
proxying_connection_pool.clear_reloadable_connections!
|
42
|
-
end
|
25
|
+
delegate :active_connections?, :clear_active_connections!,
|
26
|
+
:clear_all_connections!, :clear_reloadable_connections!,
|
27
|
+
:connected?, :connection_pool_list, :establish_connection,
|
28
|
+
:remove_connection, :retrieve_connection, :retrieve_connection_pool,
|
29
|
+
to: :retrieve_handler
|
43
30
|
|
44
31
|
def clear_all_connections!
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
# https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L568
|
50
|
-
def retrieve_connection(klass)
|
51
|
-
pool = retrieve_connection_pool klass
|
52
|
-
raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
|
53
|
-
conn = pool.connection
|
54
|
-
raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
|
55
|
-
conn
|
56
|
-
end
|
57
|
-
|
58
|
-
def connected?(klass)
|
59
|
-
pool = retrieve_connection_pool klass
|
60
|
-
pool && pool.connected?
|
61
|
-
end
|
62
|
-
|
63
|
-
def remove_connection(owner_klass)
|
64
|
-
remove_proxying_connection_pool
|
65
|
-
end
|
66
|
-
|
67
|
-
def retrieve_connection_pool(klass)
|
68
|
-
proxying_connection_pool
|
69
|
-
end
|
70
|
-
|
71
|
-
def remove_proxying_connection_pool
|
72
|
-
if proxying_pool = @process_to_connection_pool.delete(Process.pid)
|
73
|
-
proxying_pool.automatic_reconnect = false
|
74
|
-
proxying_pool.disconnect!
|
75
|
-
proxying_pool.primary_pool.spec.config
|
32
|
+
# We also want to clear the process's connection handler in case our
|
33
|
+
# configuration has been changed.
|
34
|
+
if handler = @process_to_handler.delete(Process.pid)
|
35
|
+
handler.clear_all_connections!
|
76
36
|
end
|
77
37
|
end
|
78
38
|
|
79
|
-
|
80
|
-
|
39
|
+
# Returns a `ProcessLocalConnectionHandler` which is local to the
|
40
|
+
# current process.
|
41
|
+
def retrieve_handler
|
42
|
+
@process_to_handler[Process.pid] ||= ProcessLocalConnectionHandler.new(@proxy_configuration)
|
81
43
|
end
|
82
44
|
end
|
83
45
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_record/connection_adapters/connection_specification'
|
2
|
+
|
3
|
+
module ActiveReplicas
|
4
|
+
module Rails4
|
5
|
+
module Helpers
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Returns an instance of `ActiveRecord::ConnectionAdapters::ConnectionPool`
|
9
|
+
# configured with the given specification.
|
10
|
+
def self.connection_pool_for_spec(config_spec)
|
11
|
+
@@resolver ||= ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
|
12
|
+
|
13
|
+
# Turns a hash configuration into a `ConnectionSpecification` that can
|
14
|
+
# be passed to a `ConnectionPool`.
|
15
|
+
spec = @@resolver.spec ActiveSupport::HashWithIndifferentAccess.new(config_spec)
|
16
|
+
|
17
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.new spec
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
3
|
+
require 'active_replicas/rails4/helpers'
|
4
|
+
|
5
|
+
module ActiveReplicas
|
6
|
+
module Rails4
|
7
|
+
# Provisioned for each process by `ConnectionHandler`. Each process owns
|
8
|
+
# its own pools of connections to primary and replica databases.
|
9
|
+
# Proxying connection pools are then provisioned for each thread.
|
10
|
+
class ProcessLocalConnectionHandler
|
11
|
+
attr_reader :proxy_configuration
|
12
|
+
attr_reader :primary_pool, :replica_pools
|
13
|
+
|
14
|
+
def initialize(proxy_configuration)
|
15
|
+
@proxy_configuration = proxy_configuration
|
16
|
+
|
17
|
+
@primary_pool = Helpers.connection_pool_for_spec @proxy_configuration[:primary]
|
18
|
+
|
19
|
+
@replica_pools = (@proxy_configuration[:replicas] || {}).map do |name, config_spec|
|
20
|
+
[name, Helpers.connection_pool_for_spec(config_spec)]
|
21
|
+
end.to_h
|
22
|
+
|
23
|
+
# Each thread gets its own `ProxyingConnectionPool`.
|
24
|
+
@reserved_proxies = Concurrent::Map.new
|
25
|
+
|
26
|
+
extend MonitorMixin
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a list of *all* the connection pools owned by this handler.
|
30
|
+
def connection_pool_list
|
31
|
+
[@primary_pool] + @replica_pools.values
|
32
|
+
end
|
33
|
+
|
34
|
+
def establish_connection(owner, spec)
|
35
|
+
prefix = '[ActiveReplicas::Rails4::ConnectionHandler#establish_connection]'
|
36
|
+
ActiveRecord::Base.logger&.warn "#{prefix} Ignoring spec for #{owner.inspect}: #{spec.inspect}"
|
37
|
+
ActiveRecord::Base.logger&.info "#{prefix} Called from:\n" + Kernel.caller.first(5).map {|t| " #{t}" }.join("\n")
|
38
|
+
|
39
|
+
current_proxy
|
40
|
+
end
|
41
|
+
|
42
|
+
def active_connections?
|
43
|
+
connection_pool_list.any?(&:active_connection?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear_active_connections!
|
47
|
+
synchronize do
|
48
|
+
connection_pool_list.each(&:release_connection)
|
49
|
+
clear_current_proxy
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def clear_reloadable_connections!
|
54
|
+
synchronize do
|
55
|
+
connection_pool_list.each(&:clear_reloadable_connections!)
|
56
|
+
clear_proxies!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear_all_connections!
|
61
|
+
synchronize do
|
62
|
+
connection_pool_list.each(&:disconnect!)
|
63
|
+
clear_proxies!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Cribbed from:
|
68
|
+
# https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L568
|
69
|
+
def retrieve_connection(klass)
|
70
|
+
pool = retrieve_connection_pool klass
|
71
|
+
raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
|
72
|
+
conn = pool.connection
|
73
|
+
raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
|
74
|
+
conn
|
75
|
+
end
|
76
|
+
|
77
|
+
def connected?(klass)
|
78
|
+
pool = retrieve_connection_pool klass
|
79
|
+
pool && pool.connected?
|
80
|
+
end
|
81
|
+
|
82
|
+
def remove_connection(owner_klass)
|
83
|
+
if proxy = clear_current_proxy
|
84
|
+
proxy.automatic_reconnect = false
|
85
|
+
proxy.disconnect!
|
86
|
+
proxy.spec.config
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def retrieve_connection_pool(klass)
|
91
|
+
current_proxy
|
92
|
+
end
|
93
|
+
|
94
|
+
# Semi-private implementation methdos
|
95
|
+
# ===================================
|
96
|
+
|
97
|
+
# Returns the `ThreadLocalConnectionHandler` for this thread.
|
98
|
+
def current_proxy
|
99
|
+
@reserved_proxies[current_thread_id] || synchronize do
|
100
|
+
@reserved_proxies[current_thread_id] ||= ProxyingConnectionPool.new(self)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Remove the current reserved `ProxyingConnectionPool` from the pool.
|
105
|
+
def clear_current_proxy
|
106
|
+
@reserved_proxies.delete current_thread_id
|
107
|
+
end
|
108
|
+
|
109
|
+
# Clear all reserved `ProxyingConnectionPool` instances from the pool.
|
110
|
+
def clear_proxies!
|
111
|
+
@reserved_proxies.clear
|
112
|
+
end
|
113
|
+
|
114
|
+
def current_thread_id
|
115
|
+
Thread.current.object_id
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/active_replicas.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_replicas
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dirk Gadsden
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -127,6 +127,8 @@ files:
|
|
127
127
|
- lib/active_replicas/proxying_connection.rb
|
128
128
|
- lib/active_replicas/proxying_connection_pool.rb
|
129
129
|
- lib/active_replicas/rails4/connection_handler.rb
|
130
|
+
- lib/active_replicas/rails4/helpers.rb
|
131
|
+
- lib/active_replicas/rails4/process_local_connection_handler.rb
|
130
132
|
- lib/active_replicas/railtie.rb
|
131
133
|
- lib/active_replicas/version.rb
|
132
134
|
homepage: https://github.com/dirk/active_replicas
|