seamless_database_pool 1.0.13 → 1.0.14
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/active_record/connection_adapters/seamless_database_pool_adapter.rb +28 -33
- data/lib/seamless_database_pool.rb +0 -1
- data/spec/connection_adapters_spec.rb +53 -48
- data/spec/seamless_database_pool_adapter_spec.rb +18 -12
- data/spec/test_adapter/active_record/connection_adapters/read_only_adapter.rb +1 -1
- data/spec/test_model.rb +4 -4
- metadata +4 -5
- data/lib/seamless_database_pool/connect_timeout.rb +0 -24
data/Rakefile
CHANGED
@@ -72,7 +72,7 @@ begin
|
|
72
72
|
gem.has_rdoc = true
|
73
73
|
gem.extra_rdoc_files = ["README.rdoc", "MIT-LICENSE"]
|
74
74
|
|
75
|
-
gem.add_dependency('activerecord', '>=
|
75
|
+
gem.add_dependency('activerecord', '>= 3.0.20')
|
76
76
|
gem.add_development_dependency('rspec', '>= 2.0')
|
77
77
|
gem.add_development_dependency('jeweler')
|
78
78
|
gem.add_development_dependency('sqlite3')
|
@@ -13,8 +13,6 @@ module ActiveRecord
|
|
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
|
-
master_connection.class.send(:include, SeamlessDatabasePool::ConnectTimeout) unless master_connection.class.include?(SeamlessDatabasePool::ConnectTimeout)
|
17
|
-
master_connection.connect_timeout = master_config[:connect_timeout]
|
18
16
|
pool_weights[master_connection] = master_config[:pool_weight].to_i if master_config[:pool_weight].to_i > 0
|
19
17
|
|
20
18
|
read_connections = []
|
@@ -25,8 +23,6 @@ module ActiveRecord
|
|
25
23
|
begin
|
26
24
|
establish_adapter(read_config[:adapter])
|
27
25
|
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
26
|
read_connections << conn
|
31
27
|
pool_weights[conn] = read_config[:pool_weight]
|
32
28
|
rescue Exception => e
|
@@ -93,23 +89,22 @@ module ActiveRecord
|
|
93
89
|
return const_get(adapter_class_name) if const_defined?(adapter_class_name, false)
|
94
90
|
|
95
91
|
# Define methods to proxy to the appropriate pool
|
96
|
-
read_only_methods = [:
|
92
|
+
read_only_methods = [:select, :select_rows, :execute, :tables, :columns]
|
93
|
+
clear_cache_methods = [:insert, :update, :delete]
|
94
|
+
|
95
|
+
# Get a list of all methods redefined by the underlying adapter. These will be
|
96
|
+
# proxied to the master connection.
|
97
97
|
master_methods = []
|
98
|
-
|
99
|
-
|
100
|
-
master_connection_class = master_connection.class
|
101
|
-
while ![Object, AbstractAdapter].include?(master_connection_class) do
|
102
|
-
master_connection_classes << master_connection_class
|
103
|
-
master_connection_class = master_connection_class.superclass
|
104
|
-
end
|
105
|
-
master_connection_classes.each do |connection_class|
|
98
|
+
override_classes = (master_connection.class.ancestors - AbstractAdapter.ancestors)
|
99
|
+
override_classes.each do |connection_class|
|
106
100
|
master_methods.concat(connection_class.public_instance_methods(false))
|
107
101
|
master_methods.concat(connection_class.protected_instance_methods(false))
|
108
102
|
end
|
109
|
-
master_methods.uniq
|
103
|
+
master_methods = master_methods.collect{|m| m.to_sym}.uniq
|
110
104
|
master_methods -= public_instance_methods(false) + protected_instance_methods(false) + private_instance_methods(false)
|
111
|
-
master_methods = master_methods.collect{|m| m.to_sym}
|
112
105
|
master_methods -= read_only_methods
|
106
|
+
master_methods -= [:select_all, :select_one, :select_value, :select_values]
|
107
|
+
master_methods -= clear_cache_methods
|
113
108
|
|
114
109
|
klass = Class.new(self)
|
115
110
|
master_methods.each do |method_name|
|
@@ -121,7 +116,18 @@ module ActiveRecord
|
|
121
116
|
end
|
122
117
|
EOS
|
123
118
|
end
|
124
|
-
|
119
|
+
|
120
|
+
clear_cache_methods.each do |method_name|
|
121
|
+
klass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
122
|
+
def #{method_name}(*args, &block)
|
123
|
+
clear_query_cache if query_cache_enabled
|
124
|
+
use_master_connection do
|
125
|
+
return proxy_connection_method(master_connection, :#{method_name}, :master, *args, &block)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
EOS
|
129
|
+
end
|
130
|
+
|
125
131
|
read_only_methods.each do |method_name|
|
126
132
|
klass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
127
133
|
def #{method_name}(*args, &block)
|
@@ -178,6 +184,12 @@ module ActiveRecord
|
|
178
184
|
false
|
179
185
|
end
|
180
186
|
|
187
|
+
def transaction(options = {})
|
188
|
+
use_master_connection do
|
189
|
+
super
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
181
193
|
def visitor=(visitor)
|
182
194
|
all_connections.each{|conn| conn.visitor = visitor}
|
183
195
|
end
|
@@ -213,23 +225,6 @@ module ActiveRecord
|
|
213
225
|
do_to_connections {|conn| total += conn.reset_runtime}
|
214
226
|
total
|
215
227
|
end
|
216
|
-
|
217
|
-
def lease
|
218
|
-
synchronize do
|
219
|
-
unless @in_use
|
220
|
-
@in_use = true
|
221
|
-
@last_use = Time.now
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def expire
|
227
|
-
@in_use = false
|
228
|
-
end
|
229
|
-
|
230
|
-
def close
|
231
|
-
pool.checkin self
|
232
|
-
end
|
233
228
|
|
234
229
|
# Get a random read connection from the pool. If the connection is not active, it will attempt to reconnect
|
235
230
|
# to the database. If that fails, it will be removed from the pool for one minute.
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'connect_timeout.rb')
|
2
1
|
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'connection_statistics.rb')
|
3
2
|
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'controller_filter.rb')
|
4
3
|
require File.join(File.dirname(__FILE__), 'active_record', 'connection_adapters', 'seamless_database_pool_adapter.rb')
|
@@ -13,7 +13,9 @@ describe "Test connection adapters" do
|
|
13
13
|
let(:master_connection){ connection.master_connection }
|
14
14
|
|
15
15
|
before(:all) do
|
16
|
+
ActiveRecord::Base.configurations = {'adapter' => "sqlite3", 'database' => ":memory:"}
|
16
17
|
ActiveRecord::Base.establish_connection('adapter' => "sqlite3", 'database' => ":memory:")
|
18
|
+
ActiveRecord::Base.connection
|
17
19
|
SeamlessDatabasePool::TestModel.db_model(adapter).create_tables
|
18
20
|
end
|
19
21
|
|
@@ -66,8 +68,18 @@ describe "Test connection adapters" do
|
|
66
68
|
record = model.find_by_name("test")
|
67
69
|
record.name.should == "test"
|
68
70
|
end
|
69
|
-
|
71
|
+
|
70
72
|
it "should work with query caching" do
|
73
|
+
record_id = model.first.id
|
74
|
+
model.cache do
|
75
|
+
found = model.find(record_id)
|
76
|
+
found.value.should == 1
|
77
|
+
connection.master_connection.update("UPDATE #{model.table_name} SET value = 0 WHERE id = #{record_id}")
|
78
|
+
model.find(record_id).value.should == 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should work bust the query cache on update" do
|
71
83
|
record_id = model.first.id
|
72
84
|
model.cache do
|
73
85
|
found = model.find(record_id)
|
@@ -76,7 +88,7 @@ describe "Test connection adapters" do
|
|
76
88
|
model.find(record_id).name.should == "new value"
|
77
89
|
end
|
78
90
|
end
|
79
|
-
|
91
|
+
|
80
92
|
context "read connection" do
|
81
93
|
let(:sample_sql){"SELECT #{connection.quote_column_name('name')} FROM #{connection.quote_table_name(model.table_name)}"}
|
82
94
|
|
@@ -87,43 +99,15 @@ describe "Test connection adapters" do
|
|
87
99
|
|
88
100
|
it "should send select to the read connection" do
|
89
101
|
results = connection.send(:select, sample_sql)
|
90
|
-
results.should == [{"name" => "test"}]
|
91
|
-
results.should == master_connection.send(:select, sample_sql)
|
92
|
-
results.should be_read_only
|
93
|
-
end
|
94
|
-
|
95
|
-
it "should send select_all to the read connection" do
|
96
|
-
results = connection.select_all(sample_sql)
|
97
|
-
results.should == [{"name" => "test"}]
|
98
|
-
results.should == master_connection.select_all(sample_sql)
|
99
|
-
results.should be_read_only
|
100
|
-
end
|
101
|
-
|
102
|
-
it "should send select_one to the read connection" do
|
103
|
-
results = connection.select_one(sample_sql)
|
104
|
-
results.should == {"name" => "test"}
|
105
|
-
results.should == master_connection.select_one(sample_sql)
|
106
|
-
results.should be_read_only
|
107
|
-
end
|
108
|
-
|
109
|
-
it "should send select_values to the read connection" do
|
110
|
-
results = connection.select_values(sample_sql)
|
111
|
-
results.should == ["test"]
|
112
|
-
results.should == master_connection.select_values(sample_sql)
|
113
|
-
results.should be_read_only
|
114
|
-
end
|
115
|
-
|
116
|
-
it "should send select_value to the read connection" do
|
117
|
-
results = connection.select_value(sample_sql)
|
118
|
-
results.should == "test"
|
119
|
-
results.should == master_connection.select_value(sample_sql)
|
102
|
+
results.to_a.should == [{"name" => "test"}]
|
103
|
+
results.to_a.should == master_connection.send(:select, sample_sql).to_a
|
120
104
|
results.should be_read_only
|
121
105
|
end
|
122
106
|
|
123
107
|
it "should send select_rows to the read connection" do
|
124
|
-
results = connection.
|
125
|
-
results.should == [
|
126
|
-
results.should == master_connection.
|
108
|
+
results = connection.select_rows(sample_sql)
|
109
|
+
results.should == [["test"]]
|
110
|
+
results.should == master_connection.select_rows(sample_sql)
|
127
111
|
results.should be_read_only
|
128
112
|
end
|
129
113
|
|
@@ -155,6 +139,34 @@ describe "Test connection adapters" do
|
|
155
139
|
read_connection.should be_active
|
156
140
|
end
|
157
141
|
end
|
142
|
+
|
143
|
+
context "methods not overridden" do
|
144
|
+
let(:sample_sql){"SELECT #{connection.quote_column_name('name')} FROM #{connection.quote_table_name(model.table_name)}"}
|
145
|
+
|
146
|
+
it "should use select_all" do
|
147
|
+
results = connection.select_all(sample_sql)
|
148
|
+
results.to_a.should == [{"name" => "test"}].to_a
|
149
|
+
results.to_a.should == master_connection.select_all(sample_sql).to_a
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should use select_one" do
|
153
|
+
results = connection.select_one(sample_sql)
|
154
|
+
results.should == {"name" => "test"}
|
155
|
+
results.should == master_connection.select_one(sample_sql)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should use select_values" do
|
159
|
+
results = connection.select_values(sample_sql)
|
160
|
+
results.should == ["test"]
|
161
|
+
results.should == master_connection.select_values(sample_sql)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should use select_value" do
|
165
|
+
results = connection.select_value(sample_sql)
|
166
|
+
results.should == "test"
|
167
|
+
results.should == master_connection.select_value(sample_sql)
|
168
|
+
end
|
169
|
+
end
|
158
170
|
|
159
171
|
context "master connection" do
|
160
172
|
let(:insert_sql){ "INSERT INTO #{connection.quote_table_name(model.table_name)} (#{connection.quote_column_name('name')}) VALUES ('new')" }
|
@@ -206,20 +218,13 @@ describe "Test connection adapters" do
|
|
206
218
|
end
|
207
219
|
|
208
220
|
it "should properly dump the schema" do
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
end
|
215
|
-
end
|
216
|
-
EOS
|
217
|
-
schema = schema.gsub(/^ +/, '').gsub(/ +/, ' ').strip
|
221
|
+
with_driver = StringIO.new
|
222
|
+
ActiveRecord::SchemaDumper.dump(connection, with_driver)
|
223
|
+
|
224
|
+
without_driver = StringIO.new
|
225
|
+
ActiveRecord::SchemaDumper.dump(master_connection, without_driver)
|
218
226
|
|
219
|
-
|
220
|
-
ActiveRecord::SchemaDumper.dump(connection, io)
|
221
|
-
generated_schema = io.string.gsub(/^#.*$/, '').gsub(/\n+/, "\n").gsub(/^ +/, '').gsub(/ +/, ' ').strip
|
222
|
-
generated_schema.should == schema
|
227
|
+
with_driver.string.should == without_driver.string
|
223
228
|
end
|
224
229
|
end
|
225
230
|
end
|
@@ -13,6 +13,16 @@ module SeamlessDatabasePool
|
|
13
13
|
def reconnect!
|
14
14
|
sleep(0.1)
|
15
15
|
end
|
16
|
+
|
17
|
+
def active?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def begin_db_transaction
|
22
|
+
end
|
23
|
+
|
24
|
+
def commit_db_transaction
|
25
|
+
end
|
16
26
|
end
|
17
27
|
|
18
28
|
class MockMasterConnection < MockConnection
|
@@ -118,7 +128,8 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
118
128
|
it "should use the master connection inside a transaction" do
|
119
129
|
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
120
130
|
connection = connection_class.new(nil, mock(:logger), master_connection, [read_connection_1], {read_connection_1 => 1})
|
121
|
-
master_connection.should_receive(:
|
131
|
+
master_connection.should_receive(:begin_db_transaction)
|
132
|
+
master_connection.should_receive(:commit_db_transaction)
|
122
133
|
master_connection.should_receive(:select).with('Transaction SQL', nil)
|
123
134
|
read_connection_1.should_receive(:select).with('SQL 1', nil)
|
124
135
|
read_connection_1.should_receive(:select).with('SQL 2', nil)
|
@@ -205,12 +216,7 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
205
216
|
read_connection_2.should_receive(:reconnect!)
|
206
217
|
pool_connection.reconnect!
|
207
218
|
end
|
208
|
-
|
209
|
-
it "should timeout reconnect! calls to dead servers" do
|
210
|
-
read_connection_1.connect_timeout = 0.01
|
211
|
-
lambda{read_connection_1.reconnect!}.should raise_error("reconnect timed out")
|
212
|
-
end
|
213
|
-
|
219
|
+
|
214
220
|
it "should fork reset_runtime to all connections" do
|
215
221
|
master_connection.should_receive(:reset_runtime).and_return(1)
|
216
222
|
read_connection_1.should_receive(:reset_runtime).and_return(2)
|
@@ -230,7 +236,7 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
230
236
|
end
|
231
237
|
|
232
238
|
it "should try to reconnect dead connections when they become available again" do
|
233
|
-
master_connection.stub!(:
|
239
|
+
master_connection.stub!(:select).and_raise("SQL ERROR")
|
234
240
|
master_connection.should_receive(:active?).and_return(false, false, true)
|
235
241
|
master_connection.should_receive(:reconnect!)
|
236
242
|
now = Time.now
|
@@ -242,19 +248,19 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
242
248
|
it "should not try to reconnect live connections" do
|
243
249
|
args = [:arg1, :arg2]
|
244
250
|
block = Proc.new{}
|
245
|
-
master_connection.should_receive(:
|
251
|
+
master_connection.should_receive(:select).with(*args, &block).twice.and_raise("SQL ERROR")
|
246
252
|
master_connection.should_receive(:active?).and_return(true)
|
247
253
|
master_connection.should_not_receive(:reconnect!)
|
248
|
-
lambda{pool_connection.send(:proxy_connection_method, master_connection, :
|
254
|
+
lambda{pool_connection.send(:proxy_connection_method, master_connection, :select, :read, *args, &block)}.should raise_error("SQL ERROR")
|
249
255
|
end
|
250
256
|
|
251
257
|
it "should not try to reconnect a connection during a retry" do
|
252
258
|
args = [:arg1, :arg2]
|
253
259
|
block = Proc.new{}
|
254
|
-
master_connection.should_receive(:
|
260
|
+
master_connection.should_receive(:select).with(*args, &block).and_raise("SQL ERROR")
|
255
261
|
master_connection.should_not_receive(:active?)
|
256
262
|
master_connection.should_not_receive(:reconnect!)
|
257
|
-
lambda{pool_connection.send(:proxy_connection_method, master_connection, :
|
263
|
+
lambda{pool_connection.send(:proxy_connection_method, master_connection, :select, :retry, *args, &block)}.should raise_error("SQL ERROR")
|
258
264
|
end
|
259
265
|
|
260
266
|
it "should try to execute a read statement again after a connection error" do
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
|
10
10
|
module ConnectionAdapters
|
11
11
|
class ReadOnlyAdapter < AbstractAdapter
|
12
|
-
%w(
|
12
|
+
%w(select select_rows execute tables columns).each do |read_method|
|
13
13
|
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
14
14
|
def #{read_method} (*args, &block)
|
15
15
|
raise "Not Connected" unless @connected
|
data/spec/test_model.rb
CHANGED
@@ -32,14 +32,14 @@ module SeamlessDatabasePool
|
|
32
32
|
t.column :name, :string
|
33
33
|
t.column :value, :integer
|
34
34
|
end unless table_exists?
|
35
|
-
connection.clear_cache!
|
36
|
-
undefine_attribute_methods
|
35
|
+
connection.clear_cache! if connection.respond_to?(:clear_cache!)
|
36
|
+
undefine_attribute_methods if respond_to?(:undefine_attribute_methods)
|
37
37
|
end
|
38
38
|
|
39
39
|
def drop_tables
|
40
40
|
connection.drop_table(table_name)
|
41
|
-
connection.clear_cache!
|
42
|
-
undefine_attribute_methods
|
41
|
+
connection.clear_cache! if connection.respond_to?(:clear_cache!)
|
42
|
+
undefine_attribute_methods if respond_to?(:undefine_attribute_methods)
|
43
43
|
end
|
44
44
|
|
45
45
|
def cleanup_database!
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seamless_database_pool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.14
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 3.0.20
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
29
|
+
version: 3.0.20
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: rspec
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,7 +121,6 @@ files:
|
|
121
121
|
- lib/active_record/connection_adapters/seamless_database_pool_adapter.rb
|
122
122
|
- lib/seamless_database_pool.rb
|
123
123
|
- lib/seamless_database_pool/arel_compiler.rb
|
124
|
-
- lib/seamless_database_pool/connect_timeout.rb
|
125
124
|
- lib/seamless_database_pool/connection_statistics.rb
|
126
125
|
- lib/seamless_database_pool/controller_filter.rb
|
127
126
|
- lib/seamless_database_pool/railtie.rb
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'timeout'
|
2
|
-
|
3
|
-
module SeamlessDatabasePool
|
4
|
-
# This module is mixed into connection adapters to allow the reconnect! method to timeout if the
|
5
|
-
# IP address becomes unreachable. The default timeout is 1 second, but you can change it by setting
|
6
|
-
# the connect_timeout parameter in the adapter configuration.
|
7
|
-
module ConnectTimeout
|
8
|
-
attr_accessor :connect_timeout
|
9
|
-
|
10
|
-
def self.included(base)
|
11
|
-
base.alias_method_chain :reconnect!, :connect_timeout
|
12
|
-
end
|
13
|
-
|
14
|
-
def reconnect_with_connect_timeout!
|
15
|
-
begin
|
16
|
-
timeout(connect_timeout || 1) do
|
17
|
-
reconnect_without_connect_timeout!
|
18
|
-
end
|
19
|
-
rescue Timeout::Error
|
20
|
-
raise ActiveRecord::ConnectionTimeoutError.new("reconnect timed out")
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|