seamless_database_pool 1.0.13 → 1.0.14
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/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
|