seamless_database_pool 1.0.6 → 1.0.7
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.
- data/MIT-LICENSE +20 -0
- data/Rakefile +9 -10
- data/lib/active_record/connection_adapters/seamless_database_pool_adapter.rb +70 -38
- data/lib/seamless_database_pool/arel_compiler.rb +1 -1
- data/lib/seamless_database_pool/connect_timeout.rb +5 -3
- data/lib/seamless_database_pool/connection_statistics.rb +6 -6
- data/lib/seamless_database_pool/controller_filter.rb +35 -14
- data/lib/seamless_database_pool.rb +4 -4
- data/spec/{filter_spec.rb → controller_filter_spec.rb} +55 -41
- data/spec/seamless_database_pool_adapter_spec.rb +146 -146
- metadata +16 -22
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Brian Durand
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -7,11 +7,10 @@ desc 'Default: run unit tests.'
|
|
7
7
|
task :default => :test
|
8
8
|
|
9
9
|
begin
|
10
|
-
require '
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
10
|
+
require 'rspec'
|
11
|
+
require 'rspec/core/rake_task'
|
12
|
+
desc 'Run the unit tests'
|
13
|
+
RSpec::Core::RakeTask.new(:test)
|
15
14
|
|
16
15
|
namespace :test do
|
17
16
|
desc "Run all tests including for all database adapters"
|
@@ -38,8 +37,8 @@ begin
|
|
38
37
|
|
39
38
|
namespace :adapters do
|
40
39
|
desc "Internal task to run database adapter tests"
|
41
|
-
|
42
|
-
t.
|
40
|
+
RSpec::Core::RakeTask.new(:specified) do |t|
|
41
|
+
t.pattern = FileList.new('spec/connection_adapters_spec.rb')
|
43
42
|
end
|
44
43
|
|
45
44
|
YAML.load_file(File.expand_path("../spec/database.yml", __FILE__)).keys.each do |adapter_name|
|
@@ -58,7 +57,7 @@ begin
|
|
58
57
|
end
|
59
58
|
rescue LoadError
|
60
59
|
task :test do
|
61
|
-
STDERR.puts "You must have rspec >=
|
60
|
+
STDERR.puts "You must have rspec >= 2.0 to run the tests"
|
62
61
|
end
|
63
62
|
end
|
64
63
|
|
@@ -80,10 +79,10 @@ begin
|
|
80
79
|
gem.authors = ["Brian Durand"]
|
81
80
|
gem.files = FileList["lib/**/*", "spec/**/*", "README.rdoc", "Rakefile"].to_a
|
82
81
|
gem.has_rdoc = true
|
83
|
-
gem.extra_rdoc_files = ["README.rdoc"]
|
82
|
+
gem.extra_rdoc_files = ["README.rdoc", "MIT-LICENSE"]
|
84
83
|
|
85
84
|
gem.add_dependency('activerecord', '>= 2.2.2')
|
86
|
-
gem.add_development_dependency('rspec', '>=
|
85
|
+
gem.add_development_dependency('rspec', '>= 2.0')
|
87
86
|
gem.add_development_dependency('jeweler')
|
88
87
|
end
|
89
88
|
|
@@ -1,46 +1,53 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Base
|
3
3
|
class << self
|
4
|
-
def seamless_database_pool_connection
|
4
|
+
def seamless_database_pool_connection(config)
|
5
5
|
pool_weights = {}
|
6
|
-
|
6
|
+
|
7
7
|
config = config.with_indifferent_access
|
8
8
|
default_config = {:pool_weight => 1}.merge(config.merge(:adapter => config[:pool_adapter])).with_indifferent_access
|
9
9
|
default_config.delete(:master)
|
10
10
|
default_config.delete(:read_pool)
|
11
11
|
default_config.delete(:pool_adapter)
|
12
|
-
|
12
|
+
|
13
13
|
master_config = default_config.merge(config[:master]).with_indifferent_access
|
14
14
|
establish_adapter(master_config[:adapter])
|
15
15
|
master_connection = send("#{master_config[:adapter]}_connection".to_sym, master_config)
|
16
16
|
master_connection.class.send(:include, SeamlessDatabasePool::ConnectTimeout) unless master_connection.class.include?(SeamlessDatabasePool::ConnectTimeout)
|
17
17
|
master_connection.connect_timeout = master_config[:connect_timeout]
|
18
18
|
pool_weights[master_connection] = master_config[:pool_weight].to_i if master_config[:pool_weight].to_i > 0
|
19
|
-
|
19
|
+
|
20
20
|
read_connections = []
|
21
21
|
config[:read_pool].each do |read_config|
|
22
22
|
read_config = default_config.merge(read_config).with_indifferent_access
|
23
23
|
read_config[:pool_weight] = read_config[:pool_weight].to_i
|
24
24
|
if read_config[:pool_weight] > 0
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
begin
|
26
|
+
establish_adapter(read_config[:adapter])
|
27
|
+
conn = send("#{read_config[:adapter]}_connection".to_sym, read_config)
|
28
|
+
conn.class.send(:include, SeamlessDatabasePool::ConnectTimeout) unless conn.class.include?(SeamlessDatabasePool::ConnectTimeout)
|
29
|
+
conn.connect_timeout = read_config[:connect_timeout]
|
30
|
+
read_connections << conn
|
31
|
+
pool_weights[conn] = read_config[:pool_weight]
|
32
|
+
rescue Exception => e
|
33
|
+
if logger
|
34
|
+
logger.error("Error connecting to read connection #{read_config.inspect}")
|
35
|
+
logger.error(e)
|
36
|
+
end
|
37
|
+
end
|
31
38
|
end
|
32
39
|
end if config[:read_pool]
|
33
|
-
|
40
|
+
|
34
41
|
@seamless_database_pool_classes ||= {}
|
35
42
|
klass = @seamless_database_pool_classes[master_connection.class]
|
36
43
|
unless klass
|
37
44
|
klass = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
38
45
|
@seamless_database_pool_classes[master_connection.class] = klass
|
39
46
|
end
|
40
|
-
|
47
|
+
|
41
48
|
return klass.new(nil, logger, master_connection, read_connections, pool_weights)
|
42
49
|
end
|
43
|
-
|
50
|
+
|
44
51
|
def establish_adapter (adapter)
|
45
52
|
raise AdapterNotSpecified.new("database configuration does not specify adapter") unless adapter
|
46
53
|
raise AdapterNotFound.new("database pool must specify adapters") if adapter == 'seamless_database_pool'
|
@@ -65,7 +72,7 @@ module ActiveRecord
|
|
65
72
|
end
|
66
73
|
|
67
74
|
module SeamlessDatabasePoolBehavior
|
68
|
-
def self.included
|
75
|
+
def self.included(base)
|
69
76
|
base.alias_method_chain(:reload, :seamless_database_pool)
|
70
77
|
end
|
71
78
|
|
@@ -86,7 +93,7 @@ module ActiveRecord
|
|
86
93
|
attr_reader :read_connections, :master_connection
|
87
94
|
|
88
95
|
# Create an anonymous class that extends this one and proxies methods to the pool connections.
|
89
|
-
def self.adapter_class
|
96
|
+
def self.adapter_class(master_connection)
|
90
97
|
# Define methods to proxy to the appropriate pool
|
91
98
|
read_only_methods = [:select_one, :select_all, :select_value, :select_values, :select, :select_rows, :execute, :tables, :columns]
|
92
99
|
master_methods = []
|
@@ -131,7 +138,7 @@ module ActiveRecord
|
|
131
138
|
return klass
|
132
139
|
end
|
133
140
|
|
134
|
-
def initialize
|
141
|
+
def initialize(connection, logger, master_connection, read_connections, pool_weights)
|
135
142
|
super(connection, logger)
|
136
143
|
|
137
144
|
@master_connection = master_connection
|
@@ -164,35 +171,37 @@ module ActiveRecord
|
|
164
171
|
|
165
172
|
def active?
|
166
173
|
active = true
|
167
|
-
|
174
|
+
do_to_connections {|conn| active &= conn.active?}
|
168
175
|
return active
|
169
176
|
end
|
170
|
-
|
177
|
+
|
171
178
|
def reconnect!
|
172
|
-
|
179
|
+
do_to_connections {|conn| conn.reconnect!}
|
173
180
|
end
|
174
|
-
|
181
|
+
|
175
182
|
def disconnect!
|
176
|
-
|
183
|
+
do_to_connections {|conn| conn.disconnect!}
|
177
184
|
end
|
178
|
-
|
185
|
+
|
179
186
|
def reset!
|
180
|
-
|
187
|
+
do_to_connections {|conn| conn.reset!}
|
181
188
|
end
|
182
|
-
|
189
|
+
|
183
190
|
def verify!(*ignored)
|
184
|
-
|
191
|
+
do_to_connections {|conn| conn.verify!(*ignored)}
|
185
192
|
end
|
186
|
-
|
193
|
+
|
187
194
|
def reset_runtime
|
188
|
-
|
195
|
+
total = 0.0
|
196
|
+
do_to_connections {|conn| total += conn.reset_runtime}
|
197
|
+
total
|
189
198
|
end
|
190
199
|
|
191
200
|
# Get a random read connection from the pool. If the connection is not active, it will attempt to reconnect
|
192
201
|
# to the database. If that fails, it will be removed from the pool for one minute.
|
193
202
|
def random_read_connection
|
194
203
|
weighted_read_connections = available_read_connections
|
195
|
-
if @use_master
|
204
|
+
if @use_master || weighted_read_connections.empty?
|
196
205
|
return master_connection
|
197
206
|
else
|
198
207
|
weighted_read_connections[rand(weighted_read_connections.length)]
|
@@ -235,9 +244,9 @@ module ActiveRecord
|
|
235
244
|
end
|
236
245
|
|
237
246
|
def expired?
|
238
|
-
@expires <= Time.now
|
247
|
+
@expires ? @expires <= Time.now : false
|
239
248
|
end
|
240
|
-
|
249
|
+
|
241
250
|
def reconnect!
|
242
251
|
failed_connection.reconnect!
|
243
252
|
raise DatabaseConnectionError.new unless failed_connection.active?
|
@@ -251,13 +260,23 @@ module ActiveRecord
|
|
251
260
|
available = @available_read_connections.last
|
252
261
|
if available.expired?
|
253
262
|
begin
|
254
|
-
|
255
|
-
|
263
|
+
@logger.info("Adding dead database connection back to the pool: #{available.inspect}") if @logger
|
264
|
+
available.reconnect!
|
265
|
+
rescue => e
|
256
266
|
# Couldn't reconnect so try again in a little bit
|
267
|
+
if @logger
|
268
|
+
@logger.warn("Failed to reconnect with: #{available.inspect}")
|
269
|
+
@logger.warn(e)
|
270
|
+
end
|
257
271
|
available.expires = 30.seconds.from_now
|
258
272
|
return available.connections
|
259
273
|
end
|
274
|
+
|
275
|
+
# If reconnect is successful, the connection will have been re-added to @available_read_connections list,
|
276
|
+
# so let's pop this old version of the connection
|
260
277
|
@available_read_connections.pop
|
278
|
+
|
279
|
+
# Now we'll try again after either expiring our bad connection or re-adding our good one
|
261
280
|
return available_read_connections
|
262
281
|
else
|
263
282
|
return available.connections
|
@@ -274,17 +293,19 @@ module ActiveRecord
|
|
274
293
|
end
|
275
294
|
|
276
295
|
# Temporarily remove a connection from the read pool.
|
277
|
-
def suppress_read_connection
|
296
|
+
def suppress_read_connection(conn, expire)
|
278
297
|
available = available_read_connections
|
279
298
|
connections = available.reject{|c| c == conn}
|
280
|
-
|
299
|
+
|
281
300
|
# This wasn't a read connection so don't suppress it
|
282
301
|
return if connections.length == available.length
|
283
|
-
|
302
|
+
|
284
303
|
if connections.empty?
|
304
|
+
@logger.warn("All read connections are marked dead; trying them all again.") if @logger
|
285
305
|
# No connections available so we might as well try them all again
|
286
306
|
reset_available_read_connections
|
287
307
|
else
|
308
|
+
@logger.warn("Removing #{conn.inspect} from the connection pool for #{expire} seconds") if @logger
|
288
309
|
# Available connections will now not include the suppressed connection for a while
|
289
310
|
@available_read_connections.push(AvailableConnections.new(connections, conn, expire.seconds.from_now))
|
290
311
|
end
|
@@ -292,13 +313,13 @@ module ActiveRecord
|
|
292
313
|
|
293
314
|
private
|
294
315
|
|
295
|
-
def proxy_connection_method
|
316
|
+
def proxy_connection_method(connection, method, proxy_type, *args, &block)
|
296
317
|
begin
|
297
318
|
connection.send(method, *args, &block)
|
298
319
|
rescue => e
|
299
320
|
# If the statement was a read statement and it wasn't forced against the master connection
|
300
321
|
# try to reconnect if the connection is dead and then re-run the statement.
|
301
|
-
if proxy_type == :read
|
322
|
+
if proxy_type == :read && !using_master_connection?
|
302
323
|
unless connection.active?
|
303
324
|
suppress_read_connection(connection, 30)
|
304
325
|
connection = current_read_connection
|
@@ -310,7 +331,18 @@ module ActiveRecord
|
|
310
331
|
end
|
311
332
|
end
|
312
333
|
end
|
313
|
-
|
334
|
+
|
335
|
+
# Yield a block to each connection in the pool. If the connection is dead, ignore the error
|
336
|
+
# unless it is the master connection
|
337
|
+
def do_to_connections
|
338
|
+
all_connections.each do |conn|
|
339
|
+
begin
|
340
|
+
yield(conn)
|
341
|
+
rescue => e
|
342
|
+
raise e if conn == master_connection
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
314
346
|
end
|
315
347
|
end
|
316
348
|
end
|
@@ -2,7 +2,7 @@ module Arel
|
|
2
2
|
module SqlCompiler
|
3
3
|
# Hook into arel to use the compiler used by the master connection.
|
4
4
|
class Seamless_Database_PoolCompiler < GenericCompiler
|
5
|
-
def self.new
|
5
|
+
def self.new(relation)
|
6
6
|
@compiler_classes ||= {}
|
7
7
|
master_adapter = relation.engine.connection.master_connection.adapter_name
|
8
8
|
compiler_class = @compiler_classes[master_adapter]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
1
3
|
module SeamlessDatabasePool
|
2
4
|
# This module is mixed into connection adapters to allow the reconnect! method to timeout if the
|
3
5
|
# IP address becomes unreachable. The default timeout is 1 second, but you can change it by setting
|
@@ -5,7 +7,7 @@ module SeamlessDatabasePool
|
|
5
7
|
module ConnectTimeout
|
6
8
|
attr_accessor :connect_timeout
|
7
9
|
|
8
|
-
def self.included
|
10
|
+
def self.included(base)
|
9
11
|
base.alias_method_chain :reconnect!, :connect_timeout
|
10
12
|
end
|
11
13
|
|
@@ -14,8 +16,8 @@ module SeamlessDatabasePool
|
|
14
16
|
timeout(connect_timeout || 1) do
|
15
17
|
reconnect_without_connect_timeout!
|
16
18
|
end
|
17
|
-
rescue
|
18
|
-
raise "reconnect timed out"
|
19
|
+
rescue Timeout::Error
|
20
|
+
raise ActiveRecord::ConnectionTimeoutError.new("reconnect timed out")
|
19
21
|
end
|
20
22
|
end
|
21
23
|
end
|
@@ -3,7 +3,7 @@ module SeamlessDatabasePool
|
|
3
3
|
# and it will keep track of how often each connection calls update, insert, execute,
|
4
4
|
# or select.
|
5
5
|
module ConnectionStatistics
|
6
|
-
def self.included
|
6
|
+
def self.included(base)
|
7
7
|
base.alias_method_chain(:update, :connection_statistics)
|
8
8
|
base.alias_method_chain(:insert, :connection_statistics)
|
9
9
|
base.alias_method_chain(:execute, :connection_statistics)
|
@@ -19,19 +19,19 @@ module SeamlessDatabasePool
|
|
19
19
|
@connection_statistics = {}
|
20
20
|
end
|
21
21
|
|
22
|
-
def update_with_connection_statistics
|
22
|
+
def update_with_connection_statistics(sql, name = nil)
|
23
23
|
increment_connection_statistic(:update) do
|
24
24
|
update_without_connection_statistics(sql, name)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def insert_with_connection_statistics
|
28
|
+
def insert_with_connection_statistics(sql, name = nil)
|
29
29
|
increment_connection_statistic(:insert) do
|
30
30
|
insert_without_connection_statistics(sql, name)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def execute_with_connection_statistics
|
34
|
+
def execute_with_connection_statistics(sql, name = nil)
|
35
35
|
increment_connection_statistic(:execute) do
|
36
36
|
execute_without_connection_statistics(sql, name)
|
37
37
|
end
|
@@ -39,13 +39,13 @@ module SeamlessDatabasePool
|
|
39
39
|
|
40
40
|
protected
|
41
41
|
|
42
|
-
def select_with_connection_statistics
|
42
|
+
def select_with_connection_statistics(sql, name = nil)
|
43
43
|
increment_connection_statistic(:select) do
|
44
44
|
select_without_connection_statistics(sql, name)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
def increment_connection_statistic
|
48
|
+
def increment_connection_statistic(method)
|
49
49
|
if @counting_pool_statistics
|
50
50
|
yield
|
51
51
|
else
|
@@ -13,11 +13,15 @@ module SeamlessDatabasePool
|
|
13
13
|
# ...
|
14
14
|
|
15
15
|
module ControllerFilter
|
16
|
-
def self.included
|
16
|
+
def self.included(base)
|
17
17
|
unless base.respond_to?(:use_database_pool)
|
18
18
|
base.extend(ClassMethods)
|
19
19
|
base.class_eval do
|
20
|
-
|
20
|
+
if base.method_defined?(:perform_action) || base.private_method_defined?(:perform_action)
|
21
|
+
alias_method_chain :perform_action, :seamless_database_pool
|
22
|
+
else
|
23
|
+
alias_method_chain :process, :seamless_database_pool
|
24
|
+
end
|
21
25
|
alias_method_chain :redirect_to, :seamless_database_pool
|
22
26
|
end
|
23
27
|
end
|
@@ -40,7 +44,7 @@ module SeamlessDatabasePool
|
|
40
44
|
# The configuration is inherited from parent controller classes, so if you have default
|
41
45
|
# behavior, you should simply specify it in ApplicationController to have it available
|
42
46
|
# globally.
|
43
|
-
def use_database_pool
|
47
|
+
def use_database_pool(options)
|
44
48
|
remapped_options = seamless_database_pool_options
|
45
49
|
options.each_pair do |actions, connection_method|
|
46
50
|
unless SeamlessDatabasePool::READ_CONNECTION_METHODS.include?(connection_method)
|
@@ -69,28 +73,45 @@ module SeamlessDatabasePool
|
|
69
73
|
self.class.seamless_database_pool_options
|
70
74
|
end
|
71
75
|
|
72
|
-
|
76
|
+
# Rails 3.x hook for setting the read connection for the request.
|
77
|
+
def process_with_seamless_database_pool(action, *args)
|
78
|
+
set_read_only_connection_for_block(action) do
|
79
|
+
process_without_seamless_database_pool(action, *args)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def redirect_to_with_seamless_database_pool(options = {}, response_status = {})
|
84
|
+
if SeamlessDatabasePool.read_only_connection_type(nil) == :master
|
85
|
+
use_master_db_connection_on_next_request
|
86
|
+
end
|
87
|
+
redirect_to_without_seamless_database_pool(options, response_status)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Rails 2.x hook for setting the read connection for the request.
|
93
|
+
def perform_action_with_seamless_database_pool(*args)
|
94
|
+
set_read_only_connection_for_block(action_name) do
|
95
|
+
perform_action_without_seamless_database_pool(*args)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Set the read only connection for a block. Used to set the connection for a controller action.
|
100
|
+
def set_read_only_connection_for_block(action)
|
73
101
|
read_pool_method = nil
|
74
102
|
if session
|
75
103
|
read_pool_method = session[:next_request_db_connection]
|
76
104
|
session[:next_request_db_connection] = nil
|
77
105
|
end
|
78
106
|
|
79
|
-
read_pool_method ||= seamless_database_pool_options[
|
107
|
+
read_pool_method ||= seamless_database_pool_options[action.to_sym] || seamless_database_pool_options[:all]
|
80
108
|
if read_pool_method
|
81
109
|
SeamlessDatabasePool.set_read_only_connection_type(read_pool_method) do
|
82
|
-
|
110
|
+
yield
|
83
111
|
end
|
84
112
|
else
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def redirect_to_with_seamless_database_pool (options = {}, response_status = {})
|
90
|
-
if SeamlessDatabasePool.read_only_connection_type(nil) == :master
|
91
|
-
use_master_db_connection_on_next_request
|
113
|
+
yield
|
92
114
|
end
|
93
|
-
redirect_to_without_seamless_database_pool(options, response_status)
|
94
115
|
end
|
95
116
|
end
|
96
117
|
end
|
@@ -63,7 +63,7 @@ module SeamlessDatabasePool
|
|
63
63
|
end
|
64
64
|
|
65
65
|
# Set the read only connection type to either :master, :random, or :persistent.
|
66
|
-
def set_read_only_connection_type
|
66
|
+
def set_read_only_connection_type(connection_type)
|
67
67
|
saved_connection = Thread.current[:read_only_connection]
|
68
68
|
retval = nil
|
69
69
|
begin
|
@@ -77,14 +77,14 @@ module SeamlessDatabasePool
|
|
77
77
|
end
|
78
78
|
|
79
79
|
# Get the read only connection type currently in use. Will be one of :master, :random, or :persistent.
|
80
|
-
def read_only_connection_type
|
80
|
+
def read_only_connection_type(default = :master)
|
81
81
|
connection_type = Thread.current[:read_only_connection] || default
|
82
82
|
connection_type = :persistent if connection_type.kind_of?(Hash)
|
83
83
|
return connection_type
|
84
84
|
end
|
85
85
|
|
86
86
|
# Get a read only connection from a connection pool.
|
87
|
-
def read_only_connection
|
87
|
+
def read_only_connection(pool_connection)
|
88
88
|
return pool_connection.master_connection if pool_connection.using_master_connection?
|
89
89
|
connection_type = Thread.current[:read_only_connection]
|
90
90
|
|
@@ -103,7 +103,7 @@ module SeamlessDatabasePool
|
|
103
103
|
end
|
104
104
|
|
105
105
|
# This method is provided as a way to change the persistent connection when it fails and a new one is substituted.
|
106
|
-
def set_persistent_read_connection
|
106
|
+
def set_persistent_read_connection(pool_connection, read_connection)
|
107
107
|
connection_type = Thread.current[:read_only_connection]
|
108
108
|
connection_type[pool_connection] = read_connection if connection_type.kind_of?(Hash)
|
109
109
|
end
|
@@ -1,20 +1,17 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
-
require 'cgi'
|
3
2
|
|
4
3
|
describe "SeamlessDatabasePool::ControllerFilter" do
|
5
4
|
|
6
5
|
module SeamlessDatabasePool
|
7
6
|
class TestApplicationController
|
8
|
-
attr_reader :
|
7
|
+
attr_reader :session
|
9
8
|
|
10
|
-
def initialize(
|
11
|
-
@action_name = action
|
12
|
-
session ||= {}
|
9
|
+
def initialize(session)
|
13
10
|
@session = session
|
14
11
|
end
|
15
12
|
|
16
|
-
def
|
17
|
-
send
|
13
|
+
def process(action, *args)
|
14
|
+
send action
|
18
15
|
end
|
19
16
|
|
20
17
|
def redirect_to (options = {}, response_status = {})
|
@@ -59,78 +56,95 @@ describe "SeamlessDatabasePool::ControllerFilter" do
|
|
59
56
|
redirect_to(:action => :read)
|
60
57
|
end
|
61
58
|
end
|
59
|
+
|
60
|
+
class TestRails2ApplicationController < TestApplicationController
|
61
|
+
attr_reader :action_name
|
62
|
+
|
63
|
+
def process(action, *args)
|
64
|
+
@action_name = action
|
65
|
+
perform_action
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def perform_action
|
71
|
+
send action_name
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class TestRails2BaseController < TestRails2ApplicationController
|
76
|
+
include ::SeamlessDatabasePool::ControllerFilter
|
77
|
+
|
78
|
+
use_database_pool :read => :persistent
|
79
|
+
|
80
|
+
def read
|
81
|
+
::SeamlessDatabasePool.read_only_connection_type
|
82
|
+
end
|
83
|
+
end
|
62
84
|
end
|
63
85
|
|
86
|
+
let(:session){Hash.new}
|
87
|
+
let(:controller){SeamlessDatabasePool::TestOtherController.new(session)}
|
88
|
+
|
64
89
|
it "should work with nothing set" do
|
65
|
-
controller = SeamlessDatabasePool::TestApplicationController.new(
|
66
|
-
controller.
|
90
|
+
controller = SeamlessDatabasePool::TestApplicationController.new(session)
|
91
|
+
controller.process('base_action').should == :master
|
67
92
|
end
|
68
93
|
|
69
94
|
it "should allow setting a connection type for a single action" do
|
70
|
-
controller = SeamlessDatabasePool::TestBaseController.new(
|
71
|
-
controller.
|
95
|
+
controller = SeamlessDatabasePool::TestBaseController.new(session)
|
96
|
+
controller.process('read').should == :persistent
|
72
97
|
end
|
73
98
|
|
74
99
|
it "should allow setting a connection type for actions" do
|
75
|
-
controller
|
76
|
-
controller.
|
77
|
-
controller = SeamlessDatabasePool::TestOtherController.new('save')
|
78
|
-
controller.perform_action.should == :master
|
100
|
+
controller.process('edit').should == :master
|
101
|
+
controller.process('save').should == :master
|
79
102
|
end
|
80
103
|
|
81
104
|
it "should allow setting a connection type for all actions" do
|
82
|
-
controller
|
83
|
-
controller.perform_action.should == :random
|
105
|
+
controller.process('other').should == :random
|
84
106
|
end
|
85
107
|
|
86
108
|
it "should inherit the superclass' options" do
|
87
|
-
controller
|
88
|
-
controller.perform_action.should == :persistent
|
109
|
+
controller.process('read').should == :persistent
|
89
110
|
end
|
90
111
|
|
91
112
|
it "should be able to force using the master connection on the next request" do
|
92
|
-
session = {}
|
93
|
-
|
94
113
|
# First request
|
95
|
-
controller
|
96
|
-
controller.perform_action.should == :persistent
|
114
|
+
controller.process('read').should == :persistent
|
97
115
|
controller.use_master_db_connection_on_next_request
|
98
116
|
|
99
117
|
# Second request
|
100
|
-
controller
|
101
|
-
controller.perform_action.should == :master
|
118
|
+
controller.process('read').should == :master
|
102
119
|
|
103
120
|
# Third request
|
104
|
-
controller
|
105
|
-
controller.perform_action.should == :persistent
|
121
|
+
controller.process('read').should == :persistent
|
106
122
|
end
|
107
123
|
|
108
124
|
it "should not break trying to force the master connection if sessions are not enabled" do
|
109
|
-
controller
|
110
|
-
controller.perform_action.should == :persistent
|
125
|
+
controller.process('read').should == :persistent
|
111
126
|
controller.use_master_db_connection_on_next_request
|
112
127
|
|
113
128
|
# Second request
|
114
|
-
|
115
|
-
controller.
|
129
|
+
session.clear
|
130
|
+
controller.process('read').should == :persistent
|
116
131
|
end
|
117
132
|
|
118
133
|
it "should force the master connection on the next request for a redirect in master connection block" do
|
119
|
-
|
120
|
-
controller
|
121
|
-
controller.perform_action.should == {:action => :read}
|
134
|
+
controller = SeamlessDatabasePool::TestOtherController.new(session)
|
135
|
+
controller.process('redirect_master_action').should == {:action => :read}
|
122
136
|
|
123
|
-
controller
|
124
|
-
controller.perform_action.should == :master
|
137
|
+
controller.process('read').should == :master
|
125
138
|
end
|
126
139
|
|
127
140
|
it "should not force the master connection on the next request for a redirect not in master connection block" do
|
128
|
-
|
129
|
-
controller = SeamlessDatabasePool::TestOtherController.new('redirect_read_action', session)
|
130
|
-
controller.perform_action.should == {:action => :read}
|
141
|
+
controller.process('redirect_read_action').should == {:action => :read}
|
131
142
|
|
132
|
-
controller
|
133
|
-
controller.perform_action.should == :persistent
|
143
|
+
controller.process('read').should == :persistent
|
134
144
|
end
|
135
145
|
|
146
|
+
it "should work with a Rails 2 controller" do
|
147
|
+
controller = SeamlessDatabasePool::TestRails2BaseController.new(session)
|
148
|
+
controller.process('read').should == :persistent
|
149
|
+
end
|
136
150
|
end
|
@@ -11,7 +11,7 @@ module SeamlessDatabasePool
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def reconnect!
|
14
|
-
sleep(0.
|
14
|
+
sleep(0.1)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -68,60 +68,60 @@ describe "SeamlessDatabasePoolAdapter ActiveRecord::Base extension" do
|
|
68
68
|
end
|
69
69
|
|
70
70
|
describe "SeamlessDatabasePoolAdapter" do
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
weights = {
|
77
|
-
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(
|
78
|
-
|
71
|
+
|
72
|
+
let(:master_connection){ SeamlessDatabasePool::MockMasterConnection.new("master") }
|
73
|
+
let(:read_connection_1){ SeamlessDatabasePool::MockConnection.new("read_1") }
|
74
|
+
let(:read_connection_2){ SeamlessDatabasePool::MockConnection.new("read_2") }
|
75
|
+
let(:pool_connection) do
|
76
|
+
weights = {master_connection => 1, read_connection_1 => 1, read_connection_2 => 2}
|
77
|
+
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
78
|
+
connection_class.new(nil, nil, master_connection, [read_connection_1, read_connection_2], weights)
|
79
79
|
end
|
80
80
|
|
81
81
|
context "selecting a connection from the pool" do
|
82
82
|
it "should initialize the connection pool" do
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
83
|
+
pool_connection.master_connection.should == master_connection
|
84
|
+
pool_connection.read_connections.should == [read_connection_1, read_connection_2]
|
85
|
+
pool_connection.all_connections.should == [master_connection, read_connection_1, read_connection_2]
|
86
|
+
pool_connection.pool_weight(master_connection).should == 1
|
87
|
+
pool_connection.pool_weight(read_connection_1).should == 1
|
88
|
+
pool_connection.pool_weight(read_connection_2).should == 2
|
89
89
|
end
|
90
90
|
|
91
91
|
it "should return the current read connection" do
|
92
|
-
SeamlessDatabasePool.should_receive(:read_only_connection).with(
|
93
|
-
|
92
|
+
SeamlessDatabasePool.should_receive(:read_only_connection).with(pool_connection).and_return(:current)
|
93
|
+
pool_connection.current_read_connection.should == :current
|
94
94
|
end
|
95
95
|
|
96
96
|
it "should select a random read connection" do
|
97
97
|
mock_connection = stub(:connection, :active? => true)
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
pool_connection.should_receive(:available_read_connections).and_return([:fake1, :fake2, mock_connection])
|
99
|
+
pool_connection.should_receive(:rand).with(3).and_return(2)
|
100
|
+
pool_connection.random_read_connection.should == mock_connection
|
101
101
|
end
|
102
102
|
|
103
103
|
it "should select the master connection if the read pool is empty" do
|
104
|
-
|
105
|
-
|
104
|
+
pool_connection.should_receive(:available_read_connections).and_return([])
|
105
|
+
pool_connection.random_read_connection.should == master_connection
|
106
106
|
end
|
107
107
|
|
108
108
|
it "should use the master connection in a block" do
|
109
|
-
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(
|
110
|
-
connection = connection_class.new(nil, mock(:logger),
|
111
|
-
connection.random_read_connection.should ==
|
109
|
+
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
110
|
+
connection = connection_class.new(nil, mock(:logger), master_connection, [read_connection_1], {read_connection_1 => 1})
|
111
|
+
connection.random_read_connection.should == read_connection_1
|
112
112
|
connection.use_master_connection do
|
113
|
-
connection.random_read_connection.should ==
|
113
|
+
connection.random_read_connection.should == master_connection
|
114
114
|
end
|
115
|
-
connection.random_read_connection.should ==
|
115
|
+
connection.random_read_connection.should == read_connection_1
|
116
116
|
end
|
117
117
|
|
118
118
|
it "should use the master connection inside a transaction" do
|
119
|
-
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(
|
120
|
-
connection = connection_class.new(nil, mock(:logger),
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
119
|
+
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
120
|
+
connection = connection_class.new(nil, mock(:logger), master_connection, [read_connection_1], {read_connection_1 => 1})
|
121
|
+
master_connection.should_receive(:transaction).and_yield
|
122
|
+
master_connection.should_receive(:select).with('Transaction SQL', nil)
|
123
|
+
read_connection_1.should_receive(:select).with('SQL 1', nil)
|
124
|
+
read_connection_1.should_receive(:select).with('SQL 2', nil)
|
125
125
|
|
126
126
|
SeamlessDatabasePool.use_persistent_read_connection do
|
127
127
|
connection.send(:select, 'SQL 1', nil)
|
@@ -135,87 +135,87 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
135
135
|
|
136
136
|
context "read connection methods" do
|
137
137
|
it "should proxy select methods to a read connection" do
|
138
|
-
|
139
|
-
|
140
|
-
|
138
|
+
pool_connection.should_receive(:current_read_connection).and_return(read_connection_1)
|
139
|
+
read_connection_1.should_receive(:select).with('SQL').and_return(:retval)
|
140
|
+
pool_connection.send(:select, 'SQL').should == :retval
|
141
141
|
end
|
142
142
|
|
143
143
|
it "should proxy execute methods to a read connection" do
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
pool_connection.should_receive(:current_read_connection).and_return(read_connection_1)
|
145
|
+
read_connection_1.should_receive(:execute).with('SQL').and_return(:retval)
|
146
|
+
pool_connection.execute('SQL').should == :retval
|
147
147
|
end
|
148
148
|
|
149
149
|
it "should proxy select_rows methods to a read connection" do
|
150
|
-
|
151
|
-
|
152
|
-
|
150
|
+
pool_connection.should_receive(:current_read_connection).and_return(read_connection_1)
|
151
|
+
read_connection_1.should_receive(:select_rows).with('SQL').and_return(:retval)
|
152
|
+
pool_connection.select_rows('SQL').should == :retval
|
153
153
|
end
|
154
154
|
end
|
155
155
|
|
156
156
|
context "master connection methods" do
|
157
157
|
it "should proxy insert method to the master connection" do
|
158
|
-
|
159
|
-
|
158
|
+
master_connection.should_receive(:insert).with('SQL').and_return(:retval)
|
159
|
+
pool_connection.insert('SQL').should == :retval
|
160
160
|
end
|
161
161
|
|
162
162
|
it "should proxy update method to the master connection" do
|
163
|
-
|
164
|
-
|
163
|
+
master_connection.should_receive(:update).with('SQL').and_return(:retval)
|
164
|
+
pool_connection.update('SQL').should == :retval
|
165
165
|
end
|
166
166
|
|
167
167
|
it "should proxy columns method to the master connection" do
|
168
|
-
|
169
|
-
|
168
|
+
master_connection.should_receive(:columns).with(:table).and_return(:retval)
|
169
|
+
pool_connection.columns(:table).should == :retval
|
170
170
|
end
|
171
171
|
end
|
172
172
|
|
173
173
|
context "fork to all connections" do
|
174
174
|
it "should fork active? to all connections and return true if all are up" do
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
175
|
+
master_connection.should_receive(:active?).and_return(true)
|
176
|
+
read_connection_1.should_receive(:active?).and_return(true)
|
177
|
+
read_connection_2.should_receive(:active?).and_return(true)
|
178
|
+
pool_connection.active?.should == true
|
179
179
|
end
|
180
180
|
|
181
181
|
it "should fork active? to all connections and return false if one is down" do
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
182
|
+
master_connection.should_receive(:active?).and_return(true)
|
183
|
+
read_connection_1.should_receive(:active?).and_return(true)
|
184
|
+
read_connection_2.should_receive(:active?).and_return(false)
|
185
|
+
pool_connection.active?.should == false
|
186
186
|
end
|
187
187
|
|
188
188
|
it "should fork verify! to all connections" do
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
189
|
+
master_connection.should_receive(:verify!).with(5)
|
190
|
+
read_connection_1.should_receive(:verify!).with(5)
|
191
|
+
read_connection_2.should_receive(:verify!).with(5)
|
192
|
+
pool_connection.verify!(5)
|
193
193
|
end
|
194
194
|
|
195
195
|
it "should fork disconnect! to all connections" do
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
196
|
+
master_connection.should_receive(:disconnect!)
|
197
|
+
read_connection_1.should_receive(:disconnect!)
|
198
|
+
read_connection_2.should_receive(:disconnect!)
|
199
|
+
pool_connection.disconnect!
|
200
200
|
end
|
201
201
|
|
202
202
|
it "should fork reconnect! to all connections" do
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
203
|
+
master_connection.should_receive(:reconnect!)
|
204
|
+
read_connection_1.should_receive(:reconnect!)
|
205
|
+
read_connection_2.should_receive(:reconnect!)
|
206
|
+
pool_connection.reconnect!
|
207
207
|
end
|
208
208
|
|
209
209
|
it "should timeout reconnect! calls to dead servers" do
|
210
|
-
|
211
|
-
lambda{
|
210
|
+
read_connection_1.connect_timeout = 0.01
|
211
|
+
lambda{read_connection_1.reconnect!}.should raise_error("reconnect timed out")
|
212
212
|
end
|
213
213
|
|
214
214
|
it "should fork reset_runtime to all connections" do
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
215
|
+
master_connection.should_receive(:reset_runtime).and_return(1)
|
216
|
+
read_connection_1.should_receive(:reset_runtime).and_return(2)
|
217
|
+
read_connection_2.should_receive(:reset_runtime).and_return(3)
|
218
|
+
pool_connection.reset_runtime.should == 6
|
219
219
|
end
|
220
220
|
end
|
221
221
|
|
@@ -223,135 +223,135 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
223
223
|
it "should proxy requests to a connection" do
|
224
224
|
args = [:arg1, :arg2]
|
225
225
|
block = Proc.new{}
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
226
|
+
master_connection.should_receive(:select_value).with(*args, &block)
|
227
|
+
master_connection.should_not_receive(:active?)
|
228
|
+
master_connection.should_not_receive(:reconnect!)
|
229
|
+
pool_connection.send(:proxy_connection_method, master_connection, :select_value, :master, *args, &block)
|
230
230
|
end
|
231
231
|
|
232
232
|
it "should try to reconnect dead connections when they become available again" do
|
233
|
-
|
234
|
-
|
235
|
-
|
233
|
+
master_connection.stub!(:select_value).and_raise("SQL ERROR")
|
234
|
+
master_connection.should_receive(:active?).and_return(false, false, true)
|
235
|
+
master_connection.should_receive(:reconnect!)
|
236
236
|
now = Time.now
|
237
|
-
lambda{
|
237
|
+
lambda{pool_connection.select_value("SQL")}.should raise_error("SQL ERROR")
|
238
238
|
Time.stub!(:now).and_return(now + 31)
|
239
|
-
lambda{
|
239
|
+
lambda{pool_connection.select_value("SQL")}.should raise_error("SQL ERROR")
|
240
240
|
end
|
241
241
|
|
242
242
|
it "should not try to reconnect live connections" do
|
243
243
|
args = [:arg1, :arg2]
|
244
244
|
block = Proc.new{}
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
lambda{
|
245
|
+
master_connection.should_receive(:select_value).with(*args, &block).twice.and_raise("SQL ERROR")
|
246
|
+
master_connection.should_receive(:active?).and_return(true)
|
247
|
+
master_connection.should_not_receive(:reconnect!)
|
248
|
+
lambda{pool_connection.send(:proxy_connection_method, master_connection, :select_value, :read, *args, &block)}.should raise_error("SQL ERROR")
|
249
249
|
end
|
250
250
|
|
251
251
|
it "should not try to reconnect a connection during a retry" do
|
252
252
|
args = [:arg1, :arg2]
|
253
253
|
block = Proc.new{}
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
lambda{
|
254
|
+
master_connection.should_receive(:select_value).with(*args, &block).and_raise("SQL ERROR")
|
255
|
+
master_connection.should_not_receive(:active?)
|
256
|
+
master_connection.should_not_receive(:reconnect!)
|
257
|
+
lambda{pool_connection.send(:proxy_connection_method, master_connection, :select_value, :retry, *args, &block)}.should raise_error("SQL ERROR")
|
258
258
|
end
|
259
259
|
|
260
260
|
it "should try to execute a read statement again after a connection error" do
|
261
261
|
connection_error = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter::DatabaseConnectionError.new
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
262
|
+
pool_connection.should_receive(:current_read_connection).and_return(read_connection_1)
|
263
|
+
read_connection_1.should_receive(:select).with('SQL').and_raise(connection_error)
|
264
|
+
read_connection_1.should_receive(:active?).and_return(true)
|
265
|
+
pool_connection.should_not_receive(:suppress_read_connection)
|
266
266
|
SeamlessDatabasePool.should_not_receive(:set_persistent_read_connection)
|
267
|
-
|
268
|
-
|
267
|
+
read_connection_1.should_receive(:select).with('SQL').and_return(:results)
|
268
|
+
pool_connection.send(:select, 'SQL').should == :results
|
269
269
|
end
|
270
270
|
|
271
271
|
it "should not try to execute a read statement again after a connection error if the master connection must be used" do
|
272
|
-
|
273
|
-
|
274
|
-
lambda{
|
272
|
+
master_connection.should_receive(:select).with('SQL').and_raise("Fail")
|
273
|
+
pool_connection.use_master_connection do
|
274
|
+
lambda{pool_connection.send(:select, 'SQL')}.should raise_error("Fail")
|
275
275
|
end
|
276
276
|
end
|
277
277
|
|
278
278
|
it "should not try to execute a read statement again after a non-connection error" do
|
279
|
-
|
280
|
-
|
281
|
-
lambda{
|
279
|
+
pool_connection.should_receive(:current_read_connection).and_return(read_connection_1)
|
280
|
+
pool_connection.should_receive(:proxy_connection_method).with(read_connection_1, :select, :read, 'SQL').and_raise("SQL Error")
|
281
|
+
lambda{pool_connection.send(:select, 'SQL')}.should raise_error("SQL Error")
|
282
282
|
end
|
283
283
|
|
284
284
|
it "should use a different connection on a retry if the original connection could not be reconnected" do
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
SeamlessDatabasePool.should_receive(:set_persistent_read_connection).with(
|
290
|
-
|
291
|
-
|
285
|
+
pool_connection.should_receive(:current_read_connection).and_return(read_connection_1, read_connection_2)
|
286
|
+
read_connection_1.should_receive(:select).with('SQL').and_raise("Fail")
|
287
|
+
read_connection_1.should_receive(:active?).and_return(false)
|
288
|
+
pool_connection.should_receive(:suppress_read_connection).with(read_connection_1, 30)
|
289
|
+
SeamlessDatabasePool.should_receive(:set_persistent_read_connection).with(pool_connection, read_connection_2)
|
290
|
+
read_connection_2.should_receive(:select).with('SQL').and_return(:results)
|
291
|
+
pool_connection.send(:select, 'SQL').should == :results
|
292
292
|
end
|
293
293
|
|
294
294
|
it "should keep track of read connections that can't be reconnected for a set period" do
|
295
|
-
|
296
|
-
|
297
|
-
|
295
|
+
pool_connection.available_read_connections.should include(read_connection_1)
|
296
|
+
pool_connection.suppress_read_connection(read_connection_1, 30)
|
297
|
+
pool_connection.available_read_connections.should_not include(read_connection_1)
|
298
298
|
end
|
299
299
|
|
300
300
|
it "should return dead connections to the pool after the timeout has expired" do
|
301
|
-
|
302
|
-
|
303
|
-
|
301
|
+
pool_connection.available_read_connections.should include(read_connection_1)
|
302
|
+
pool_connection.suppress_read_connection(read_connection_1, 0.2)
|
303
|
+
pool_connection.available_read_connections.should_not include(read_connection_1)
|
304
304
|
sleep(0.3)
|
305
|
-
|
305
|
+
pool_connection.available_read_connections.should include(read_connection_1)
|
306
306
|
end
|
307
307
|
|
308
308
|
it "should not return a connection to the pool until it can be reconnected" do
|
309
|
-
|
310
|
-
|
311
|
-
|
309
|
+
pool_connection.available_read_connections.should include(read_connection_1)
|
310
|
+
pool_connection.suppress_read_connection(read_connection_1, 0.2)
|
311
|
+
pool_connection.available_read_connections.should_not include(read_connection_1)
|
312
312
|
sleep(0.3)
|
313
|
-
|
314
|
-
|
315
|
-
|
313
|
+
read_connection_1.should_receive(:reconnect!)
|
314
|
+
read_connection_1.should_receive(:active?).and_return(false)
|
315
|
+
pool_connection.available_read_connections.should_not include(read_connection_1)
|
316
316
|
end
|
317
317
|
|
318
318
|
it "should try all connections again if none of them can be reconnected" do
|
319
|
-
stack =
|
319
|
+
stack = pool_connection.instance_variable_get(:@available_read_connections)
|
320
320
|
|
321
|
-
available =
|
322
|
-
available.should include(
|
323
|
-
available.should include(
|
324
|
-
available.should include(
|
321
|
+
available = pool_connection.available_read_connections
|
322
|
+
available.should include(read_connection_1)
|
323
|
+
available.should include(read_connection_2)
|
324
|
+
available.should include(master_connection)
|
325
325
|
stack.size.should == 1
|
326
326
|
|
327
|
-
|
328
|
-
available =
|
329
|
-
available.should_not include(
|
330
|
-
available.should include(
|
331
|
-
available.should include(
|
327
|
+
pool_connection.suppress_read_connection(read_connection_1, 30)
|
328
|
+
available = pool_connection.available_read_connections
|
329
|
+
available.should_not include(read_connection_1)
|
330
|
+
available.should include(read_connection_2)
|
331
|
+
available.should include(master_connection)
|
332
332
|
stack.size.should == 2
|
333
333
|
|
334
|
-
|
335
|
-
available =
|
336
|
-
available.should_not include(
|
337
|
-
available.should include(
|
338
|
-
available.should_not include(
|
334
|
+
pool_connection.suppress_read_connection(master_connection, 30)
|
335
|
+
available = pool_connection.available_read_connections
|
336
|
+
available.should_not include(read_connection_1)
|
337
|
+
available.should include(read_connection_2)
|
338
|
+
available.should_not include(master_connection)
|
339
339
|
stack.size.should == 3
|
340
340
|
|
341
|
-
|
342
|
-
available =
|
343
|
-
available.should include(
|
344
|
-
available.should include(
|
345
|
-
available.should include(
|
341
|
+
pool_connection.suppress_read_connection(read_connection_2, 30)
|
342
|
+
available = pool_connection.available_read_connections
|
343
|
+
available.should include(read_connection_1)
|
344
|
+
available.should include(read_connection_2)
|
345
|
+
available.should include(master_connection)
|
346
346
|
stack.size.should == 1
|
347
347
|
end
|
348
348
|
|
349
349
|
it "should not try to suppress a read connection that wasn't available in the read pool" do
|
350
|
-
stack =
|
350
|
+
stack = pool_connection.instance_variable_get(:@available_read_connections)
|
351
351
|
stack.size.should == 1
|
352
|
-
|
352
|
+
pool_connection.suppress_read_connection(read_connection_1, 30)
|
353
353
|
stack.size.should == 2
|
354
|
-
|
354
|
+
pool_connection.suppress_read_connection(read_connection_1, 30)
|
355
355
|
stack.size.should == 2
|
356
356
|
end
|
357
357
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seamless_database_pool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 7
|
10
|
+
version: 1.0.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Brian Durand
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-06-23 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -42,12 +42,11 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
hash:
|
45
|
+
hash: 3
|
46
46
|
segments:
|
47
|
-
- 1
|
48
47
|
- 2
|
49
|
-
-
|
50
|
-
version:
|
48
|
+
- 0
|
49
|
+
version: "2.0"
|
51
50
|
type: :development
|
52
51
|
version_requirements: *id002
|
53
52
|
- !ruby/object:Gem::Dependency
|
@@ -71,6 +70,7 @@ executables: []
|
|
71
70
|
extensions: []
|
72
71
|
|
73
72
|
extra_rdoc_files:
|
73
|
+
- MIT-LICENSE
|
74
74
|
- README.rdoc
|
75
75
|
files:
|
76
76
|
- README.rdoc
|
@@ -83,20 +83,21 @@ files:
|
|
83
83
|
- lib/seamless_database_pool/controller_filter.rb
|
84
84
|
- spec/connection_adapters_spec.rb
|
85
85
|
- spec/connection_statistics_spec.rb
|
86
|
+
- spec/controller_filter_spec.rb
|
86
87
|
- spec/database.yml
|
87
|
-
- spec/filter_spec.rb
|
88
88
|
- spec/seamless_database_pool_adapter_spec.rb
|
89
89
|
- spec/seamless_database_pool_spec.rb
|
90
90
|
- spec/spec_helper.rb
|
91
91
|
- spec/test_adapter/active_record/connection_adapters/read_only_adapter.rb
|
92
92
|
- spec/test_model.rb
|
93
|
+
- MIT-LICENSE
|
93
94
|
has_rdoc: true
|
94
95
|
homepage: http://github.com/bdurand/seamless_database_pool
|
95
96
|
licenses: []
|
96
97
|
|
97
98
|
post_install_message:
|
98
|
-
rdoc_options:
|
99
|
-
|
99
|
+
rdoc_options: []
|
100
|
+
|
100
101
|
require_paths:
|
101
102
|
- lib
|
102
103
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -120,16 +121,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
121
|
requirements: []
|
121
122
|
|
122
123
|
rubyforge_project:
|
123
|
-
rubygems_version: 1.
|
124
|
+
rubygems_version: 1.5.2
|
124
125
|
signing_key:
|
125
126
|
specification_version: 3
|
126
127
|
summary: Add support for master/slave database clusters in ActiveRecord to improve performance.
|
127
|
-
test_files:
|
128
|
-
|
129
|
-
- spec/connection_statistics_spec.rb
|
130
|
-
- spec/filter_spec.rb
|
131
|
-
- spec/seamless_database_pool_adapter_spec.rb
|
132
|
-
- spec/seamless_database_pool_spec.rb
|
133
|
-
- spec/spec_helper.rb
|
134
|
-
- spec/test_adapter/active_record/connection_adapters/read_only_adapter.rb
|
135
|
-
- spec/test_model.rb
|
128
|
+
test_files: []
|
129
|
+
|