active_replicas 0.3.2 → 0.4.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
  SHA1:
3
- metadata.gz: 173a69eb3b3d994aceb621ee0ab906ce00ffa351
4
- data.tar.gz: 7a4b43fae106392d6ae07e572bcb4ab99bd7b0bb
3
+ metadata.gz: e56f8bd047e2f96b83a8246b4cdbcd428dd4e095
4
+ data.tar.gz: e02a7e2680c7bc60aa47c2cd75a25ca704f3a5c5
5
5
  SHA512:
6
- metadata.gz: 4fa4f73e1a7edfcab0650b91627672de911677e90d516df5d095fdb7edb374fc896325557659b3b065398b8f9590a02f67e1d5b5986f19ddf884215c9cd5cf9f
7
- data.tar.gz: 2e3592d77e410716166849b648a9295ac22430911a2363f81c658c032982009ae6662b6ffc6031a050821181810f4ae4270607473f9fafc7762f3e419b758036
6
+ metadata.gz: f430544ad26fd4f894addafb5961ea1cfc5aabf99fc565a206556bf70d302efe7afc4b4ae4447345c95c4ec1793d95d109400aa1995b7e72bfe4c1c644d2276e
7
+ data.tar.gz: 755fc9c0a43f3860d4c8f2f5b7ef984740f401a51b10dc50c29f0da1bf964648df7d813457272529ab9da73177ce4e2a94db7e64b9e03d622fa305b0e8c57f66
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- active_replicas (0.3.2)
4
+ active_replicas (0.4.0)
5
5
  concurrent-ruby (~> 1.0)
6
6
 
7
7
  GEM
@@ -1,5 +1,6 @@
1
- require 'monitor'
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 :primary_pool, :replica_pools
15
+ attr_reader :handler
12
16
 
13
- def initialize(proxy_configuration)
14
- @primary_pool = ProxyingConnectionPool.connection_pool_for_spec proxy_configuration[:primary]
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
- # Returns an instance of `ActiveRecord::ConnectionAdapters::ConnectionPool`
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 == @primary_pool,
39
+ is_primary: pool == primary_pool,
51
40
  proxy: self
52
41
  end
53
42
 
54
43
  def active_connection?
55
- synchronize do
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
- synchronize do
62
- each_pool &:release_connection
48
+ all_pools.each(&:release_connection)
63
49
 
64
- @primary_depth = 0
65
- @current_pool = nil
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
- synchronize do
75
- current_pool.connected?
76
- end
59
+ current_pool.connected?
77
60
  end
78
61
 
79
62
  def disconnect!
80
- synchronize do
81
- each_pool &:disconnect!
82
- end
63
+ all_pools.each(&:disconnect!)
83
64
  end
84
65
 
85
66
  def clear_reloadable_connections!
86
- synchronize do
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
- each_pool do |pool|
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
- @primary_pool.connections
88
+ primary_pool.connections
110
89
  end
111
90
 
112
91
  def spec
113
- @primary_pool.spec
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 = @primary_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
- # `clear_active_connections!` is called! If you want to *temporarily*
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 = @primary_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, owned
129
+ # Returns an `Enumerable` over all the pools, primary and replicas, used
151
130
  # by this proxying pool.
152
131
  def all_pools
153
- [ @primary_pool ] + @replica_pools.values
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 @primary_pool if @primary_pool.connections.any? { |c| c.object_id == object_id }
136
+ return primary_pool if primary_pool.connections.any? { |c| c.object_id == object_id }
162
137
 
163
- @replica_pools.values.each do |pool|
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 == @primary_pool
146
+ pool == primary_pool
172
147
  end
173
148
 
174
149
  def replica_pool?(pool)
175
- @replica_pools.values.include? pool
150
+ replica_pools.values.include? pool
176
151
  end
177
152
 
178
153
  private
179
154
 
180
155
  def next_pool
181
- replicas = @replica_pools.values
156
+ replicas = replica_pools.values
182
157
 
183
158
  if replicas.empty?
184
- @primary_pool
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 map of connection keys to database
16
- # connection instances.
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
- def active_connections?
33
- proxying_connection_pool.active_connection?
34
- end
35
-
36
- def clear_active_connections!
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
- proxying_connection_pool.disconnect!
46
- end
47
-
48
- # Cribbed from:
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
- def proxying_connection_pool
80
- @process_to_connection_pool[Process.pid] ||= ProxyingConnectionPool.new(@proxy_configuration)
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
@@ -1,3 +1,3 @@
1
1
  module ActiveReplicas
2
- VERSION = '0.3.2'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -7,6 +7,7 @@ if defined? ActiveRecord
7
7
  version = ActiveRecord::VERSION::MAJOR
8
8
 
9
9
  if version == 4
10
+ require 'active_replicas/rails4/helpers'
10
11
  require 'active_replicas/rails4/connection_handler'
11
12
  else
12
13
  raise "Unsupported ActiveRecord version: #{version}"
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.3.2
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-03 00:00:00.000000000 Z
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