activerecord-bogacs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +26 -0
- data/Gemfile +33 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +167 -0
- data/activerecord-bogacs.gemspec +26 -0
- data/lib/active_record/bogacs.rb +55 -0
- data/lib/active_record/bogacs/default_pool.rb +672 -0
- data/lib/active_record/bogacs/false_pool.rb +259 -0
- data/lib/active_record/bogacs/pool_support.rb +21 -0
- data/lib/active_record/bogacs/shareable_pool.rb +255 -0
- data/lib/active_record/bogacs/version.rb +5 -0
- data/lib/active_record/connection_adapters/adapter_compat.rb +57 -0
- data/lib/active_record/shared_connection.rb +24 -0
- data/test/active_record/bogacs/default_pool_test.rb +34 -0
- data/test/active_record/bogacs/false_pool_test.rb +200 -0
- data/test/active_record/bogacs/shareable_pool/connection_pool_test.rb +186 -0
- data/test/active_record/bogacs/shareable_pool/connection_sharing_test.rb +429 -0
- data/test/active_record/bogacs/shareable_pool_helper.rb +81 -0
- data/test/active_record/builtin_pool_test.rb +18 -0
- data/test/active_record/connection_pool_test_methods.rb +336 -0
- data/test/test_helper.rb +304 -0
- metadata +130 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
AbstractAdapter.class_eval do
|
6
|
+
|
7
|
+
attr_accessor :pool unless method_defined? :pool
|
8
|
+
|
9
|
+
unless method_defined? :owner
|
10
|
+
|
11
|
+
attr_reader :owner
|
12
|
+
|
13
|
+
if method_defined? :in_use?
|
14
|
+
|
15
|
+
def lease
|
16
|
+
unless in_use?
|
17
|
+
@owner = Thread.current; @in_use = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def expire
|
22
|
+
@in_use = false; @owner = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
else
|
26
|
+
|
27
|
+
alias :in_use? :owner
|
28
|
+
|
29
|
+
def lease
|
30
|
+
unless in_use?
|
31
|
+
@owner = Thread.current
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def expire
|
36
|
+
@owner = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
alias :in_use? :owner
|
42
|
+
|
43
|
+
def lease
|
44
|
+
unless in_use?
|
45
|
+
@owner = Thread.current
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def expire
|
50
|
+
@owner = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module SharedConnection
|
3
|
+
|
4
|
+
def with_shared_connection(&block)
|
5
|
+
ActiveRecord::SharedConnection.with_shared_connection(self.class, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
# NOTE: shareable pool loaded and setup to be used (from an initializer) :
|
9
|
+
if ActiveRecord::Base.connection_pool.respond_to?(:with_shared_connection)
|
10
|
+
|
11
|
+
def self.with_shared_connection(model = ActiveRecord::Base, &block)
|
12
|
+
model.connection_pool.with_shared_connection(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
else
|
16
|
+
|
17
|
+
def self.with_shared_connection(model = ActiveRecord::Base, &block)
|
18
|
+
model.connection_pool.with_connection(&block) # default pool is used
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path('../../test_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
ActiveRecord::Bogacs::DefaultPool.class_eval do
|
4
|
+
# ...
|
5
|
+
end
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module Bogacs
|
9
|
+
class DefaultPoolTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
include ConnectionAdapters::ConnectionPoolTestMethods
|
12
|
+
|
13
|
+
def config; AR_CONFIG end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
super
|
17
|
+
@pool = DefaultPool.new ActiveRecord::Base.connection_pool.spec
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_prefills_initial_connections
|
21
|
+
@pool.disconnect!
|
22
|
+
spec = ActiveRecord::Base.connection_pool.spec.dup
|
23
|
+
spec.instance_variable_set :@config, spec.config.merge(:pool_initial => 1.0)
|
24
|
+
@pool = DefaultPool.new spec
|
25
|
+
assert_equal @pool.size, @pool.connections.size
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_does_not_prefill_connections_by_default
|
29
|
+
assert_equal 0, @pool.connections.size
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require File.expand_path('../../test_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'atomic'
|
4
|
+
|
5
|
+
ActiveRecord::Bogacs::FalsePool.class_eval do
|
6
|
+
# ...
|
7
|
+
end
|
8
|
+
|
9
|
+
module ActiveRecord
|
10
|
+
module Bogacs
|
11
|
+
class FalsePool
|
12
|
+
|
13
|
+
class TestBase < ::Test::Unit::TestCase
|
14
|
+
# extend Bogacs::TestHelper
|
15
|
+
extend Bogacs::JndiTestHelper
|
16
|
+
|
17
|
+
def self.startup
|
18
|
+
return if self == TestBase
|
19
|
+
|
20
|
+
ActiveRecord::Base.establish_connection AR_CONFIG
|
21
|
+
|
22
|
+
ActiveRecord::Base.connection.jdbc_connection # force connection
|
23
|
+
current_config = Bogacs::TestHelper.current_connection_config
|
24
|
+
|
25
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
26
|
+
|
27
|
+
setup_jdbc_context
|
28
|
+
bind_data_source init_data_source current_config
|
29
|
+
|
30
|
+
ConnectionAdapters::ConnectionHandler.connection_pool_class = FalsePool
|
31
|
+
ActiveRecord::Base.establish_connection jndi_config
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.shutdown
|
35
|
+
return if self == TestBase
|
36
|
+
|
37
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
38
|
+
ConnectionAdapters::ConnectionHandler.connection_pool_class = ConnectionAdapters::ConnectionPool
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class ConnectionPoolWrappingTomcatJdbcTest < TestBase
|
44
|
+
|
45
|
+
@@data_source = nil
|
46
|
+
def self.init_data_source(config)
|
47
|
+
@@data_source = init_tomcat_jdbc_data_source(config)
|
48
|
+
end
|
49
|
+
|
50
|
+
include ConnectionAdapters::ConnectionPoolTestMethods
|
51
|
+
|
52
|
+
def setup
|
53
|
+
@pool = FalsePool.new ActiveRecord::Base.connection_pool.spec
|
54
|
+
end
|
55
|
+
|
56
|
+
def teardown
|
57
|
+
@@data_source.send(:close, true) if @@data_source
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_uses_false_pool_and_can_execute_query
|
61
|
+
assert_instance_of ActiveRecord::Bogacs::FalsePool, ActiveRecord::Base.connection_pool
|
62
|
+
assert ActiveRecord::Base.connection.exec_query('SELECT 42')
|
63
|
+
end
|
64
|
+
|
65
|
+
# adjust ConnectionAdapters::ConnectionPoolTestMethods :
|
66
|
+
|
67
|
+
undef :test_checkout_fairness
|
68
|
+
undef :test_checkout_fairness_by_group
|
69
|
+
|
70
|
+
undef :test_released_connection_moves_between_threads
|
71
|
+
|
72
|
+
undef :test_reap_inactive
|
73
|
+
|
74
|
+
undef :test_automatic_reconnect= # or does automatic_reconnect make sense?
|
75
|
+
|
76
|
+
undef :test_removing_releases_latch
|
77
|
+
|
78
|
+
# @override
|
79
|
+
def test_remove_connection
|
80
|
+
conn = pool.checkout
|
81
|
+
assert conn.in_use?
|
82
|
+
|
83
|
+
#length = pool.connections.size
|
84
|
+
pool.remove conn
|
85
|
+
assert conn.in_use?
|
86
|
+
#assert_equal(length - 1, pool.connections.length)
|
87
|
+
ensure
|
88
|
+
conn.close if conn
|
89
|
+
end
|
90
|
+
|
91
|
+
# @override
|
92
|
+
def test_full_pool_exception
|
93
|
+
# ~ pool_size.times { pool.checkout }
|
94
|
+
threads_ready = Queue.new; threads_block = Atomic.new(0); threads = []
|
95
|
+
pool_size.times do |i|
|
96
|
+
threads << Thread.new do
|
97
|
+
begin
|
98
|
+
conn = ActiveRecord::Base.connection
|
99
|
+
threads_block.update { |v| v + 1 }
|
100
|
+
threads_ready << i
|
101
|
+
while threads_block.value != -1 # await
|
102
|
+
sleep(0.005)
|
103
|
+
end
|
104
|
+
rescue => e
|
105
|
+
puts "block thread failed: #{e.inspect}"
|
106
|
+
ensure
|
107
|
+
conn && conn.close
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
pool_size.times { threads_ready.pop } # awaits
|
112
|
+
|
113
|
+
assert_raise(ConnectionTimeoutError) do
|
114
|
+
ActiveRecord::Base.connection # ~ pool.checkout
|
115
|
+
end
|
116
|
+
|
117
|
+
ensure
|
118
|
+
#connection && connection.close
|
119
|
+
threads_block && threads_block.swap(-1)
|
120
|
+
threads && threads.each(&:join)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @override
|
124
|
+
def test_full_pool_blocks
|
125
|
+
t1_ready = Queue.new; t1_block = Queue.new
|
126
|
+
t1 = Thread.new do
|
127
|
+
begin
|
128
|
+
conn = ActiveRecord::Base.connection
|
129
|
+
t1_ready.push(conn)
|
130
|
+
t1_block.pop # await
|
131
|
+
rescue => e
|
132
|
+
puts "t1 thread failed: #{e.inspect}"
|
133
|
+
ensure
|
134
|
+
conn && conn.close
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
threads_ready = Queue.new; threads_block = Atomic.new(0); threads = []
|
139
|
+
(pool_size - 1).times do |i|
|
140
|
+
threads << Thread.new do
|
141
|
+
begin
|
142
|
+
conn = ActiveRecord::Base.connection
|
143
|
+
threads_block.update { |v| v + 1 }
|
144
|
+
threads_ready << i
|
145
|
+
while threads_block.value != -1 # await
|
146
|
+
sleep(0.005)
|
147
|
+
end
|
148
|
+
rescue => e
|
149
|
+
puts "block thread failed: #{e.inspect}"
|
150
|
+
ensure
|
151
|
+
conn && conn.close
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
(pool_size - 1).times { threads_ready.pop } # awaits
|
156
|
+
|
157
|
+
connection = t1_ready.pop
|
158
|
+
t1_jdbc_connection = connection.jdbc_connection(true)
|
159
|
+
|
160
|
+
# pool = ActiveRecord::Base.connection_pool
|
161
|
+
|
162
|
+
t2 = Thread.new do
|
163
|
+
begin
|
164
|
+
ActiveRecord::Base.connection
|
165
|
+
rescue => e
|
166
|
+
puts "t2 thread failed: #{e.inspect}"
|
167
|
+
end
|
168
|
+
end; sleep(0.1)
|
169
|
+
|
170
|
+
# make sure our thread is in the timeout section
|
171
|
+
# Thread.pass until t2.status == "sleep"
|
172
|
+
|
173
|
+
sleep(0.02); assert t2.alive?
|
174
|
+
sleep(0.03); assert t2.alive?
|
175
|
+
|
176
|
+
t1_block.push(:release); t1.join
|
177
|
+
|
178
|
+
sleep(0.01); assert_not_equal 'sleep', t2.status
|
179
|
+
|
180
|
+
if defined? JRUBY_VERSION
|
181
|
+
if connection2 = t2.join.value
|
182
|
+
assert_equal t1_jdbc_connection, connection2.jdbc_connection(true)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
ensure
|
187
|
+
#connection && connection.close
|
188
|
+
threads_block && threads_block.swap(-1)
|
189
|
+
threads && threads.each(&:join)
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def pool_size; @@data_source.max_active end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require File.expand_path('../shareable_pool_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Bogacs
|
5
|
+
class ShareablePool
|
6
|
+
|
7
|
+
class ConnectionPoolTest < TestBase
|
8
|
+
|
9
|
+
include ConnectionAdapters::ConnectionPoolTestMethods
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@pool = ShareablePool.new ActiveRecord::Base.connection_pool.spec
|
13
|
+
end
|
14
|
+
|
15
|
+
if ActiveRecord::VERSION::MAJOR < 4
|
16
|
+
# TODO override with similar (back-ported) tests :
|
17
|
+
undef :test_remove_connection
|
18
|
+
undef :test_remove_connection_for_thread
|
19
|
+
undef :test_removing_releases_latch
|
20
|
+
|
21
|
+
undef :test_reap_and_active
|
22
|
+
undef :test_reap_inactive
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class PoolAPITest < TestBase
|
28
|
+
|
29
|
+
def setup; ActiveRecord::Base.connection end
|
30
|
+
# def teardown; ActiveRecord::Base.connection_pool.reap end
|
31
|
+
|
32
|
+
def test_is_setup
|
33
|
+
assert ActiveRecord::Base.connection_pool.is_a? ShareablePool
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_can_checkout_a_connection
|
37
|
+
assert ActiveRecord::Base.connection.exec_query('SELECT 42')
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_connected?
|
41
|
+
assert ActiveRecord::Base.connected?
|
42
|
+
begin
|
43
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
44
|
+
assert ! ActiveRecord::Base.connected?
|
45
|
+
ensure
|
46
|
+
ActiveRecord::Base.connection
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_active?
|
51
|
+
conn = ActiveRecord::Base.connection
|
52
|
+
conn.exec_query('SELECT 42')
|
53
|
+
assert conn.active?
|
54
|
+
assert reserved_connections.size > 0
|
55
|
+
begin
|
56
|
+
ActiveRecord::Base.clear_active_connections!
|
57
|
+
assert_equal 0, reserved_connections.size
|
58
|
+
ensure
|
59
|
+
ActiveRecord::Base.connection
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_disconnect!
|
64
|
+
ActiveRecord::Base.connection
|
65
|
+
threads = []
|
66
|
+
threads << Thread.new { ActiveRecord::Base.connection }
|
67
|
+
threads << Thread.new { ActiveRecord::Base.connection }
|
68
|
+
threads.each(&:join)
|
69
|
+
|
70
|
+
begin
|
71
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
72
|
+
assert_equal 0, reserved_connections.size
|
73
|
+
assert_equal 0, connections.size
|
74
|
+
assert_equal 0, shared_connections.size
|
75
|
+
ensure
|
76
|
+
ActiveRecord::Base.connection
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_clear_reloadable_connections!
|
81
|
+
ActiveRecord::Base.connection
|
82
|
+
threads = []
|
83
|
+
threads << Thread.new { ActiveRecord::Base.connection }
|
84
|
+
threads << Thread.new { ActiveRecord::Base.connection }
|
85
|
+
threads.each(&:join)
|
86
|
+
|
87
|
+
begin
|
88
|
+
ActiveRecord::Base.connection_pool.clear_reloadable_connections!
|
89
|
+
assert_equal 0, reserved_connections.size
|
90
|
+
assert connections.size > 0 # returned
|
91
|
+
assert_equal 0, shared_connections.size
|
92
|
+
ensure
|
93
|
+
ActiveRecord::Base.connection
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_remove
|
98
|
+
conn = ActiveRecord::Base.connection
|
99
|
+
assert connections.include? conn
|
100
|
+
begin
|
101
|
+
connection_pool.remove(conn)
|
102
|
+
refute connections.include?(conn)
|
103
|
+
refute shared_connection?(conn)
|
104
|
+
ensure
|
105
|
+
ActiveRecord::Base.connection
|
106
|
+
end
|
107
|
+
end if ActiveRecord::VERSION::MAJOR >= 4
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
class PoolAPIWithSharedConnectionTest < PoolAPITest
|
112
|
+
|
113
|
+
def setup
|
114
|
+
with_shared_connection do
|
115
|
+
@shared_connection = ActiveRecord::Base.connection
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def teardown
|
120
|
+
connection_pool.release_shared_connection(@shared_connection)
|
121
|
+
super
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
class CustomAPITest < TestBase
|
127
|
+
|
128
|
+
def setup
|
129
|
+
connection_pool.disconnect!
|
130
|
+
end
|
131
|
+
|
132
|
+
def teardown
|
133
|
+
clear_active_connections!; clear_shared_connections!
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_with_shared_connection
|
137
|
+
shared_connection = nil
|
138
|
+
assert shared_connections.empty?
|
139
|
+
begin
|
140
|
+
with_shared_connection do |connection|
|
141
|
+
assert shared_connection = connection
|
142
|
+
assert shared_connections.get(connection)
|
143
|
+
assert connections.include?(connection)
|
144
|
+
end
|
145
|
+
ensure
|
146
|
+
connection_pool.remove(shared_connection) if shared_connection
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_with_shared_connection_disconnected
|
151
|
+
shared_connection = nil
|
152
|
+
begin
|
153
|
+
connections_size = connections.size
|
154
|
+
with_shared_connection do |connection|
|
155
|
+
assert shared_connection = connection
|
156
|
+
assert shared_connection?(connection)
|
157
|
+
assert_equal connections_size + 1, connections.size
|
158
|
+
end
|
159
|
+
ensure
|
160
|
+
connection_pool.remove(shared_connection) if shared_connection
|
161
|
+
ActiveRecord::Base.connection
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_release_shared_connection
|
166
|
+
begin
|
167
|
+
with_shared_connection do |connection|
|
168
|
+
assert shared_connection?(connection)
|
169
|
+
refute available_connection?(connection)
|
170
|
+
|
171
|
+
connection_pool.release_shared_connection(connection)
|
172
|
+
|
173
|
+
refute shared_connection?(connection)
|
174
|
+
assert available_connection?(connection)
|
175
|
+
end
|
176
|
+
ensure
|
177
|
+
connection_pool.disconnect!
|
178
|
+
ActiveRecord::Base.connection
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|