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