seamless_database_pool 1.0.6 → 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|