activerecord-bogacs 0.1.0 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63fff373845781b28c456f85fe34f88f4bf70a50
4
- data.tar.gz: 6b3a8ce661500ec191eff3405d6d3e92d9d90c9e
3
+ metadata.gz: 6cade13cd15b5374a780d1f8d0de1d63b7e0c89a
4
+ data.tar.gz: 51949daaccd651b2d8fa51ccfa3379109885ed16
5
5
  SHA512:
6
- metadata.gz: ba7c559f09102200a324592448dd7f283ac2b604dd1cdbc5f8e3a0309c07181ebcfbb7fbb576dfa3421cff9faf1cb2c3c5907b64c519c3dbe6da8ab039daaa01
7
- data.tar.gz: bfc98ebdacaab29b8d73be82da0327af06bab79d861426c538106de8918bd3ca90581bde24112d1ce4cce76bb3027d186d14ff9f3f017a7205a262696b8c5657
6
+ metadata.gz: 91259b8c14a9eeea273eddb83c0b561ab65908683619a6b4f0bd7d744198c7a9e1a1b4dae5d1705b82b98fa5e8c0097d00b06c9b1c90ca7113cbfa1b59018fa7
7
+ data.tar.gz: 9f814b2b2eb6ced23c2da439c2dfd05c9fec270ed50f2f410748c75906e9911f62a7e794b28e5021de0de9eec5169b13d381dd077fba94fe1f39b159b6bebfde
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  jdk:
3
- - openjdk6
3
+ #- openjdk6
4
4
  - oraclejdk7
5
5
  - oraclejdk8
6
6
  rvm:
@@ -8,19 +8,19 @@ rvm:
8
8
  #- jruby-head
9
9
  #- jruby-18mode
10
10
  #- jruby-19mode
11
- #- 2.1.0
11
+ #- 2.1.2
12
12
  before_install:
13
13
  - ((jruby -v | grep 1.8.7) && jruby --1.9 -S gem update --system 2.1.11) || true
14
14
  before_script:
15
- - echo "JRUBY_OPTS: $JRUBY_OPTS"
15
+ #- echo \"JRUBY_OPTS: $JRUBY_OPTS\"
16
16
  - export JRUBY_OPTS="--server -Xcext.enabled=false -Xcompile.invokedynamic=false"
17
17
  - export JAVA_OPTS="$JAVA_OPTS" # -Xmx600M
18
- - echo "JAVA_OPTS: $JAVA_OPTS"
19
18
  script:
20
19
  - bundle exec rake tomcat:jndi:download tomcat:jdbc:download tomcat:dbcp:download
20
+ - bundle exec rake c3p0:download hikari:download
21
21
  - bundle exec rake db:create:mysql db:create:postgresql
22
22
  env:
23
- - JRUBY_OPTS="--1.8 $JRUBY_OPTS" AR_ADAPTER=mysql
23
+ - JRUBY_OPTS="$JRUBY_OPTS" AR_ADAPTER=mysql AR_VERSION="~> 4.1.6"
24
+ - JRUBY_OPTS="$JRUBY_OPTS" AR_ADAPTER=postgresql AR_VERSION="~> 4.1.6"
24
25
  - JRUBY_OPTS="$JRUBY_OPTS" AR_ADAPTER=postgresql AR_VERSION="~> 3.2.18"
25
26
  - JRUBY_OPTS="--1.8 $JRUBY_OPTS" AR_ADAPTER=mysql AR_VERSION="~> 3.2.18"
26
- - JRUBY_OPTS="$JRUBY_OPTS" AR_ADAPTER=postgresql
data/Gemfile CHANGED
@@ -16,7 +16,21 @@ else
16
16
  gem 'activerecord', :require => nil
17
17
  end
18
18
 
19
- gem 'activerecord-jdbc-adapter', :require => nil, :platform => :jruby
19
+ platform :jruby do
20
+ if version = ENV['AR_JDBC_VERSION']
21
+ if version.index('/') && ::File.exist?(version)
22
+ gem 'activerecord-jdbc-adapter', :path => version
23
+ elsif version =~ /^[0-9abcdef]+$/
24
+ gem 'activerecord-jdbc-adapter', :github => 'jruby/activerecord-jdbc-adapter', :ref => version
25
+ elsif version.index('.').nil?
26
+ gem 'activerecord-jdbc-adapter', :github => 'jruby/activerecord-jdbc-adapter', :branch => version
27
+ else
28
+ gem 'activerecord-jdbc-adapter', version, :require => nil
29
+ end
30
+ else
31
+ gem 'activerecord-jdbc-adapter', :require => nil
32
+ end
33
+ end
20
34
 
21
35
  #gem 'thread_safe', :require => nil # "optional" - we can roll without it
22
36
 
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # ActiveRecord::Bogacs
2
2
 
3
- ActiveRecord pooling "hacks" ... in a relaxed 'spa' fashion!
3
+ ActiveRecord (all-year) pooling "alternatives" ... in a relaxed 'spa' fashion.
4
4
 
5
5
  ![Bogacs][0]
6
6
 
7
7
  Bogács is a village in Borsod-Abaúj-Zemplén county, Hungary.
8
8
 
9
- **WiP: don't even thing about putting this on production (TEST IT OUT FIRST)!**
9
+ **WiP: do not put this on production if you do not understand the consequences!**
10
10
 
11
11
  ## Install
12
12
 
@@ -34,7 +34,7 @@ module MyApp
34
34
  end
35
35
  end
36
36
 
37
- # sample configuration using the "false" pool
37
+ # sample AR-Bogacs setup using the "false" pool :
38
38
  if Rails.env.production?
39
39
  pool_class = ActiveRecord::Bogacs::FalsePool
40
40
  ActiveRecord::ConnectionAdapters::ConnectionHandler.connection_pool_class = pool_class
@@ -50,13 +50,13 @@ facing potential (pool related) concurrency bugs e.g. with high-loads under JRub
50
50
 
51
51
  Based on pool code from 4.x (which works much better than any previous version),
52
52
  with a few minor tunings and extensions such as `pool_initial: 0.5` which allows
53
- to specify how many connections to prefill when the pool is created.
53
+ to specify how many connections to initialize in advance when the pool is created.
54
54
 
55
55
  ### False Pool
56
56
 
57
- The false pool won't do any actual pooling, it is assumes that an underlying pool
58
- is configured. But it does maintain a cache of AR connections mapped to threads.
59
- Ignores all pool related configuration `pool: 42`, `checkout_timeout: 2.5` etc.
57
+ The false pool won't do any actual pooling, it is assumed that an underlying pool
58
+ is configured. Still, it does maintain a hash of AR connections mapped to threads.
59
+ Ignores pool related configurations such as `pool: 42` or `checkout_timeout: 2.5`.
60
60
 
61
61
  **NOTE:** be sure to configure an underlying pool e.g. with Trinidad (using the
62
62
  default Tomcat JDBC pool) :
@@ -74,7 +74,7 @@ default Tomcat JDBC pool) :
74
74
  initialSize: <%= ENV['POOL_INITIAL'] || 25 %> # connections created on start
75
75
  maxActive: <%= ENV['POOL_SIZE'] || 100 %> # default 100 (AR pool: size)
76
76
  maxIdle: <%= ENV['POOL_SIZE'] || 100 %> # max connections kept in the pool
77
- minIdle: <%= ENV['POOL_SIZE'] || 50 %>
77
+ minIdle: <%= ENV['POOL_INITIAL'] || 50 %>
78
78
  # idle connections are checked periodically (if enabled) and connections
79
79
  # that been idle for longer than minEvictableIdleTimeMillis will be released
80
80
  minEvictableIdleTimeMillis: <%= 3 * 60 * 1000 %> # default 60s
@@ -91,8 +91,7 @@ production:
91
91
  jndi: java:/comp/env/jdbc/BogacsDB
92
92
  ```
93
93
 
94
- **NOTE:** using the `FalsePool` there's nothing to configure (in *database.yml*)
95
- that is `no_pool: 5` or `checkout_timeout: 5` defaults!
94
+ **NOTE:** when using `FalsePool` there's nothing to configure (in *database.yml*)!
96
95
 
97
96
  ### Shareable Pool
98
97
 
data/Rakefile CHANGED
@@ -10,8 +10,8 @@ task :default => :test
10
10
  desc "Creates a (test) MySQL database"
11
11
  task 'db:create:mysql' do
12
12
  fail "could not create database: mysql executable not found" unless mysql = _which('mysql')
13
- ENV['Rake'] = true; ENV['AR_ADAPTER'] ||= 'mysql'
14
- load File.expand_path('test/test_helper.rb', File.dirname(__FILE__))
13
+ ENV['Rake'] = true.to_s; ENV['AR_ADAPTER'] ||= 'mysql'
14
+ require File.expand_path('test/test_helper.rb', File.dirname(__FILE__))
15
15
 
16
16
  script = "DROP DATABASE IF EXISTS `#{AR_CONFIG[:database]}`;"
17
17
  script << "CREATE DATABASE `#{AR_CONFIG[:database]}` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;"
@@ -20,22 +20,20 @@ task 'db:create:mysql' do
20
20
  script << "SET PASSWORD FOR #{AR_CONFIG[:username]}@localhost = PASSWORD('#{AR_CONFIG[:password]}');"
21
21
  end
22
22
  params = { '-u' => 'root' }
23
- if ENV['DATABASE_YML']
24
- require 'yaml'
23
+ if ENV['DATABASE_YML']; require 'yaml'
25
24
  password = YAML.load(File.new(ENV['DATABASE_YML']))["production"]["password"]
26
25
  params['--password'] = password
27
26
  end
28
27
  puts "Creating MySQL database: #{AR_CONFIG[:database]}"
29
28
  sh "cat #{_sql_script(script).path} | #{mysql} #{params.to_a.join(' ')}", :verbose => $VERBOSE
30
- puts "... run tests with MySQL using: `rake test AR_ADAPTER=mysql`"
29
+ # puts "... run tests with MySQL using: `AR_ADAPTER=mysql rake test `"
31
30
  end
32
31
 
33
32
  desc "Creates a (test) PostgreSQL database"
34
33
  task 'db:create:postgresql' do
35
34
  fail 'could not create database: psql executable not found' unless psql = _which('psql')
36
- fail 'could not create database: missing "postgres" role' unless PostgresHelper.postgres_role?
37
- ENV['Rake'] = true; ENV['AR_ADAPTER'] ||= 'postgresql'
38
- load File.expand_path('test/test_helper.rb', File.dirname(__FILE__))
35
+ ENV['Rake'] = true.to_s; ENV['AR_ADAPTER'] ||= 'postgresql'
36
+ require File.expand_path('test/test_helper.rb', File.dirname(__FILE__))
39
37
 
40
38
  script = "DROP DATABASE IF EXISTS #{AR_CONFIG[:database]};"
41
39
  if pg_user = AR_CONFIG[:username] || ENV['PGUSER'] || ENV_JAVA['user.name']
@@ -44,10 +42,15 @@ task 'db:create:postgresql' do
44
42
  script << "CREATE USER #{pg_user} CREATEDB SUPERUSER LOGIN PASSWORD '#{pg_password}';"
45
43
  end
46
44
  script << "CREATE DATABASE #{AR_CONFIG[:database]} OWNER #{pg_user || 'postgres'};"
47
- params = { '-U' => ENV['PSQL_USER'] || 'postgres' }; params['-q'] = nil unless $VERBOSE
45
+
46
+ psql_params = "-U #{ENV['PSQL_USER'] || 'postgres'}"
47
+ psql_params << " -h #{ENV['PGHOST']}" if ENV['PGHOST']
48
+ psql_params << " -p #{ENV['PGPORT']}" if ENV['PGPORT']
49
+ psql_params << " -q " unless $VERBOSE
50
+
48
51
  puts "Creating PostgreSQL database: #{AR_CONFIG[:database]}"
49
- sh "cat #{_sql_script(script).path} | #{psql} #{params.to_a.join(' ')}", :verbose => $VERBOSE
50
- puts "... run tests with PostgreSQL using: `rake test AR_ADAPTER=postgresql`"
52
+ sh "cat #{_sql_script(script).path} | #{psql} #{psql_params}", :verbose => $VERBOSE
53
+ # puts "... run tests with PostgreSQL using: `AR_ADAPTER=postgresql rake test `"
51
54
  end
52
55
 
53
56
  def _sql_script(content, name = '_sql_script')
@@ -71,6 +74,22 @@ def _which(cmd)
71
74
  nil
72
75
  end
73
76
 
77
+ def _download(uri, download_dir, as_file_name)
78
+ require 'open-uri'; require 'tmpdir'
79
+
80
+ temp_dir = File.join(Dir.tmpdir, (Time.now.to_f * 1000).to_i.to_s)
81
+ FileUtils.mkdir temp_dir
82
+
83
+ Dir.chdir(temp_dir) do
84
+ FileUtils.mkdir download_dir unless File.exist?(download_dir)
85
+ puts "downloading #{uri}"
86
+ file = open(uri)
87
+ FileUtils.cp file.path, File.join(download_dir, as_file_name)
88
+ end
89
+
90
+ FileUtils.rm_r temp_dir
91
+ end
92
+
74
93
  namespace :tomcat do
75
94
 
76
95
  tomcat_maven_repo = 'http://repo2.maven.org/maven2/org/apache/tomcat'
@@ -87,19 +106,7 @@ namespace :tomcat do
87
106
 
88
107
  uri = "#{tomcat_maven_repo}/#{tomcat_pool}/#{version}/#{tomcat_pool}-#{version}.jar"
89
108
 
90
- require 'open-uri'; require 'tmpdir'
91
-
92
- temp_dir = File.join(Dir.tmpdir, (Time.now.to_f * 1000).to_i.to_s)
93
- FileUtils.mkdir temp_dir
94
-
95
- Dir.chdir(temp_dir) do
96
- FileUtils.mkdir download_dir unless File.exist?(download_dir)
97
- puts "downloading #{uri}"
98
- file = open(uri)
99
- FileUtils.cp file.path, File.join(download_dir, tomcat_pool_jar)
100
- end
101
-
102
- FileUtils.rm_r temp_dir
109
+ _download(uri, download_dir, tomcat_pool_jar)
103
110
  end
104
111
 
105
112
  task :check do
@@ -165,3 +172,69 @@ namespace :tomcat do
165
172
  end
166
173
 
167
174
  end
175
+
176
+ namespace :c3p0 do
177
+
178
+ mchange_base_repo = 'http://repo2.maven.org/maven2/com/mchange'
179
+ download_dir = File.expand_path('test/jars', File.dirname(__FILE__))
180
+ c3p0_version = '0.9.5-pre9'
181
+ mchange_commons_version = '0.2.8'
182
+
183
+ c3p0_jar = "c3p0-#{c3p0_version}.jar"
184
+ mchange_commons_jar = "mchange-commons-java-#{mchange_commons_version}.jar"
185
+
186
+ task :download, :version do |_,args| # rake c3p0:download
187
+ # version = args[:version] || version_default
188
+
189
+ uri = "#{mchange_base_repo}/mchange-commons-java/#{mchange_commons_version}/#{mchange_commons_jar}"
190
+
191
+ _download(uri, download_dir, mchange_commons_jar)
192
+
193
+ uri = "#{mchange_base_repo}/c3p0/#{c3p0_version}/#{c3p0_jar}"
194
+
195
+ _download(uri, download_dir, c3p0_jar)
196
+ end
197
+
198
+ task :clear do
199
+ Dir.glob( File.join(download_dir, '{c3p0,mchange-commons}*.jar') ).each { |jar| rm jar }
200
+ end
201
+
202
+ end
203
+
204
+ namespace :hikari do
205
+
206
+ hikari_base_repo = 'http://repo2.maven.org/maven2/com/zaxxer'
207
+ slf4j_base_repo = 'http://repo2.maven.org/maven2/org/slf4j'
208
+ javassist_base_repo = 'http://repo2.maven.org/maven2/org/javassist'
209
+ download_dir = File.expand_path('test/jars', File.dirname(__FILE__))
210
+ hikari_version = '1.3.9'
211
+ slf4j_version = '1.7.7'
212
+ javassist_version = '3.18.2-GA'
213
+
214
+ slf4j_api_jar = "slf4j-api-#{slf4j_version}.jar"
215
+ slf4j_simple_jar = "slf4j-simple-#{slf4j_version}.jar"
216
+ javassist_jar = "javassist-#{javassist_version}.jar"
217
+
218
+ task :download, :version do |_,args| # rake c3p0:download
219
+ version = args[:version] || hikari_version
220
+
221
+ hikari_jar = "HikariCP-#{hikari_version}.jar"
222
+
223
+ uri = "#{hikari_base_repo}/HikariCP/#{version}/#{hikari_jar}"
224
+ _download(uri, download_dir, hikari_jar)
225
+
226
+ uri = "#{slf4j_base_repo}/slf4j-api/#{slf4j_version}/#{slf4j_api_jar}"
227
+ _download(uri, download_dir, slf4j_api_jar)
228
+
229
+ uri = "#{slf4j_base_repo}/slf4j-simple/#{slf4j_version}/#{slf4j_simple_jar}"
230
+ _download(uri, download_dir, slf4j_simple_jar)
231
+
232
+ uri = "#{javassist_base_repo}/javassist/#{javassist_version}/#{javassist_jar}"
233
+ _download(uri, download_dir, javassist_jar)
234
+ end
235
+
236
+ task :clear do
237
+ Dir.glob( File.join(download_dir, '{HikariCP,slf4j}*.jar') ).each { |jar| rm jar }
238
+ end
239
+
240
+ end
@@ -220,7 +220,7 @@ module ActiveRecord
220
220
  rescue ConnectionTimeoutError => e
221
221
  raise e
222
222
  rescue => e
223
- raise ConnectionTimeoutError, e.message if _timeout_error?(e)
223
+ raise ConnectionTimeoutError, e.message if timeout_error?(e)
224
224
  raise e
225
225
  end
226
226
  conn.pool = self
@@ -233,27 +233,30 @@ module ActiveRecord
233
233
  # conn
234
234
  #end
235
235
 
236
- # sample on JRuby + Tomcat JDBC :
237
- # ActiveRecord::JDBCError(<The driver encountered an unknown error:
238
- # org.apache.tomcat.jdbc.pool.PoolExhaustedException:
239
- # [main] Timeout: Pool empty. Unable to fetch a connection in 2 seconds,
240
- # none available[size:10; busy:10; idle:0; lastwait:2500].>
241
- # )
242
-
243
- def _timeout_error?(error)
244
- error.inspect =~ /timeout/i
236
+ def timeout_error?(error)
237
+ full_error = error.inspect
238
+ # sample on JRuby + Tomcat JDBC :
239
+ # ActiveRecord::JDBCError(<The driver encountered an unknown error:
240
+ # org.apache.tomcat.jdbc.pool.PoolExhaustedException:
241
+ # [main] Timeout: Pool empty. Unable to fetch a connection in 2 seconds,
242
+ # none available[size:10; busy:10; idle:0; lastwait:2500].>
243
+ # )
244
+ return true if full_error =~ /timeout/i
245
+ # C3P0 :
246
+ # java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
247
+ return true if full_error =~ /timed.?out/i
248
+ # NOTE: not sure what to do on MRI and friends (C-pools not tested)
249
+ false
245
250
  end
246
251
 
247
- # def _timeout_error?(error); end # TODO: not sure what to do on MRI and friends
248
- #
249
- # def _timeout_error?(error)
250
- # if error.is_a?(JDBCError)
251
- # if sql_exception = error.sql_exception
252
- # return true if sql_exception.to_s =~ /timeout/i
253
- # end
254
- # end
255
- # end if defined? ArJdbc
252
+ #def timeout_error?(error)
253
+ # if error.is_a?(JDBCError)
254
+ # if sql_exception = error.sql_exception
255
+ # return true if sql_exception.to_s =~ /timeout/i
256
+ # end
257
+ # end
258
+ #end if defined? ArJdbc
256
259
 
257
260
  end
258
261
  end
259
- end
262
+ end
@@ -13,9 +13,29 @@ module ActiveRecord
13
13
  end
14
14
 
15
15
  def current_connection_id
16
- Base.connection_id ||= Thread.current.object_id # TODO
16
+ # NOTE: possible fiber work-around on JRuby ?!
17
+ Base.connection_id ||= Thread.current.object_id
17
18
  end
18
19
 
20
+ # @note Method not part of the pre 4.0 API (does no exist).
21
+ def remove(conn)
22
+ synchronize do
23
+ @connections.delete conn
24
+ release conn
25
+ end
26
+ end if ActiveRecord::VERSION::MAJOR < 4
27
+
28
+ # clear_stale_cached_connections! without the deprecation :
29
+ def reap
30
+ keys = @reserved_connections.keys -
31
+ Thread.list.find_all { |t| t.alive? }.map(&:object_id)
32
+ keys.each do |key|
33
+ conn = @reserved_connections[key]
34
+ checkin conn
35
+ @reserved_connections.delete(key)
36
+ end
37
+ end if ActiveRecord::VERSION::MAJOR < 4
38
+
19
39
  end
20
40
  end
21
41
  end
@@ -1,8 +1,11 @@
1
1
  require 'active_record/connection_adapters/abstract/connection_pool'
2
+
2
3
  require 'thread'
3
4
  require 'thread_safe'
4
5
  require 'atomic'
5
6
 
7
+ require 'active_record/bogacs/pool_support'
8
+
6
9
  # NOTE: needs explicit configuration - before connection gets established e.g.
7
10
  #
8
11
  # pool_class = ActiveRecord::ConnectionAdapters::ShareableConnectionPool
@@ -10,8 +13,9 @@ require 'atomic'
10
13
  #
11
14
  module ActiveRecord
12
15
  module Bogacs
13
- class ShareablePool < ConnectionAdapters::ConnectionPool # TODO do not override?!
16
+ class ShareablePool < ConnectionAdapters::ConnectionPool # NOTE: maybe do not override?!
14
17
  include ThreadSafe::Util::CheapLockable
18
+ include PoolSupport
15
19
 
16
20
  DEFAULT_SHARED_POOL = 0.25 # only allow 25% of the pool size to be shared
17
21
  MAX_THREAD_SHARING = 5 # not really a strict limit but should hold
@@ -31,15 +35,14 @@ module ActiveRecord
31
35
 
32
36
  # @override
33
37
  def connection
34
- # TODO we assume here a single pool - multiple pool support not implemented!
35
- Thread.current[:shared_pool_connection] || begin # super - simplified :
38
+ Thread.current[shared_connection_key] || begin # super - simplified :
36
39
  super # @reserved_connections.compute_if_absent(current_connection_id) { checkout }
37
40
  end
38
41
  end
39
42
 
40
43
  # @override
41
44
  def active_connection?
42
- if shared_conn = Thread.current[:shared_pool_connection]
45
+ if shared_conn = Thread.current[shared_connection_key]
43
46
  return shared_conn.in_use?
44
47
  end
45
48
  super_active_connection? current_connection_id
@@ -95,8 +98,9 @@ module ActiveRecord
95
98
  # Custom API :
96
99
 
97
100
  def release_shared_connection(connection)
98
- if connection == Thread.current[:shared_pool_connection]
99
- Thread.current[:shared_pool_connection] = nil
101
+ shared_conn_key = shared_connection_key
102
+ if connection == Thread.current[shared_conn_key]
103
+ Thread.current[shared_conn_key] = nil
100
104
  end
101
105
 
102
106
  @shared_connections.delete(connection)
@@ -104,8 +108,9 @@ module ActiveRecord
104
108
  end
105
109
 
106
110
  def with_shared_connection
111
+ shared_conn_key = shared_connection_key
107
112
  # with_shared_connection call nested in the same thread
108
- if connection = Thread.current[:shared_pool_connection]
113
+ if connection = Thread.current[shared_conn_key]
109
114
  emulated_checkout(connection)
110
115
  return yield connection
111
116
  end
@@ -138,12 +143,12 @@ module ActiveRecord
138
143
  shared = true
139
144
  end
140
145
 
141
- Thread.current[:shared_pool_connection] = connection if shared
146
+ Thread.current[shared_conn_key] = connection if shared
142
147
 
143
148
  DEBUG && debug("with_shared_conn obtaining a connection took #{(Time.now - start) * 1000}ms")
144
149
  yield connection
145
150
  ensure
146
- Thread.current[:shared_pool_connection] = nil # if shared
151
+ Thread.current[shared_conn_key] = nil # if shared
147
152
  rem_shared_connection(connection) if shared
148
153
  end
149
154
  end
@@ -162,10 +167,21 @@ module ActiveRecord
162
167
 
163
168
  def acquire_connection_no_wait?
164
169
  synchronize do
165
- @available.send(:can_remove_no_wait?) || @connections.size < @size
170
+ @connections.size < @size || @available.send(:can_remove_no_wait?)
171
+ #return true if @connections.size < @size
172
+ # @connections.size < @size || Queue#can_remove_no_wait? :
173
+ #queue = @available.instance_variable_get(:@queue)
174
+ #num_waiting = @available.instance_variable_get(:@num_waiting)
175
+ #queue.size > num_waiting
166
176
  end
167
177
  end
168
178
 
179
+ def acquire_connection_no_wait?
180
+ synchronize do
181
+ @connections.size < @size || @connections.any? { |c| ! c.in_use? }
182
+ end
183
+ end if ActiveRecord::VERSION::MAJOR < 4
184
+
169
185
  # get a (shared) connection that is least shared among threads (or nil)
170
186
  # nil gets returned if it's 'better' to checkout a new one to be shared
171
187
  # ... to better utilize shared connection reuse among multiple threads
@@ -231,6 +247,10 @@ module ActiveRecord
231
247
  connection.lease; # connection.verify! auto-reconnect should do this
232
248
  end
233
249
 
250
+ def shared_connection_key
251
+ @shared_connection_key ||= :"shared_pool_connection##{object_id}"
252
+ end
253
+
234
254
  DEBUG = begin
235
255
  debug = ENV['DB_POOL_DEBUG'].to_s
236
256
  if debug.to_s == 'false' then false
@@ -252,4 +272,4 @@ module ActiveRecord
252
272
 
253
273
  end
254
274
  end
255
- end
275
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Bogacs
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -12,10 +12,24 @@ module ActiveRecord
12
12
 
13
13
  if method_defined? :in_use?
14
14
 
15
- def lease
16
- unless in_use?
17
- @owner = Thread.current; @in_use = true
15
+ if method_defined? :last_use
16
+
17
+ def lease
18
+ unless in_use?
19
+ @owner = Thread.current
20
+ @in_use = true; @last_use = Time.now
21
+ end
18
22
  end
23
+
24
+ else
25
+
26
+ def lease
27
+ unless in_use?
28
+ @owner = Thread.current
29
+ @in_use = true
30
+ end
31
+ end
32
+
19
33
  end
20
34
 
21
35
  def expire
@@ -38,18 +52,6 @@ module ActiveRecord
38
52
 
39
53
  end
40
54
 
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
55
  end
54
56
 
55
57
  end
@@ -14,54 +14,72 @@ module ActiveRecord
14
14
  # extend Bogacs::TestHelper
15
15
  extend Bogacs::JndiTestHelper
16
16
 
17
+ @@data_source = nil
18
+
17
19
  def self.startup
18
20
  return if self == TestBase
19
21
 
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
22
  ConnectionAdapters::ConnectionHandler.connection_pool_class = FalsePool
31
- ActiveRecord::Base.establish_connection jndi_config
23
+
24
+ establish_jndi_connection
32
25
  end
33
26
 
34
27
  def self.shutdown
35
28
  return if self == TestBase
36
29
 
30
+ close_data_source
31
+
37
32
  ActiveRecord::Base.connection_pool.disconnect!
38
33
  ConnectionAdapters::ConnectionHandler.connection_pool_class = ConnectionAdapters::ConnectionPool
39
34
  end
40
35
 
41
- end
36
+ @@raw_config = nil
42
37
 
43
- class ConnectionPoolWrappingTomcatJdbcTest < TestBase
38
+ def self.raw_config
39
+ @@raw_config ||= begin
40
+ ActiveRecord::Base.establish_connection AR_CONFIG
44
41
 
45
- @@data_source = nil
46
- def self.init_data_source(config)
47
- @@data_source = init_tomcat_jdbc_data_source(config)
42
+ ActiveRecord::Base.connection.jdbc_connection # force connection
43
+ current_config = Bogacs::TestHelper.current_connection_config
44
+
45
+ ActiveRecord::Base.connection_pool.disconnect!
46
+
47
+ current_config
48
+ end
48
49
  end
49
50
 
50
- include ConnectionAdapters::ConnectionPoolTestMethods
51
+ def self.init_data_source
52
+ setup_jdbc_context
53
+ bind_data_source @@data_source = build_data_source(raw_config)
54
+ end
51
55
 
52
- def setup
53
- @pool = FalsePool.new ActiveRecord::Base.connection_pool.spec
56
+ def self.establish_jndi_connection
57
+ if ActiveRecord::Base.connected?
58
+ ActiveRecord::Base.clear_all_connections!
59
+ close_data_source
60
+ end
61
+
62
+ init_data_source
63
+ ActiveRecord::Base.establish_connection jndi_config
54
64
  end
55
65
 
56
- def teardown
57
- @@data_source.send(:close, true) if @@data_source
66
+ def self.close_data_source
67
+ @@data_source.close if @@data_source
58
68
  end
59
69
 
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')
70
+ def data_source; @@data_source end
71
+
72
+ end
73
+
74
+ module ConnectionPoolWrappingDataSourceTestMethods
75
+ include ConnectionAdapters::ConnectionPoolTestMethods
76
+
77
+ def setup
78
+ @pool = FalsePool.new ActiveRecord::Base.connection_pool.spec
63
79
  end
64
80
 
81
+ def max_pool_size; raise "#{__method__} not implemented" end
82
+
65
83
  # adjust ConnectionAdapters::ConnectionPoolTestMethods :
66
84
 
67
85
  undef :test_checkout_fairness
@@ -75,6 +93,11 @@ module ActiveRecord
75
93
 
76
94
  undef :test_removing_releases_latch
77
95
 
96
+ def test_uses_false_pool_and_can_execute_query
97
+ assert_instance_of ActiveRecord::Bogacs::FalsePool, ActiveRecord::Base.connection_pool
98
+ assert ActiveRecord::Base.connection.exec_query('SELECT 42')
99
+ end
100
+
78
101
  # @override
79
102
  def test_remove_connection
80
103
  conn = pool.checkout
@@ -92,7 +115,7 @@ module ActiveRecord
92
115
  def test_full_pool_exception
93
116
  # ~ pool_size.times { pool.checkout }
94
117
  threads_ready = Queue.new; threads_block = Atomic.new(0); threads = []
95
- pool_size.times do |i|
118
+ max_pool_size.times do |i|
96
119
  threads << Thread.new do
97
120
  begin
98
121
  conn = ActiveRecord::Base.connection
@@ -108,7 +131,7 @@ module ActiveRecord
108
131
  end
109
132
  end
110
133
  end
111
- pool_size.times { threads_ready.pop } # awaits
134
+ max_pool_size.times { threads_ready.pop } # awaits
112
135
 
113
136
  assert_raise(ConnectionTimeoutError) do
114
137
  ActiveRecord::Base.connection # ~ pool.checkout
@@ -136,7 +159,7 @@ module ActiveRecord
136
159
  end
137
160
 
138
161
  threads_ready = Queue.new; threads_block = Atomic.new(0); threads = []
139
- (pool_size - 1).times do |i|
162
+ (max_pool_size - 1).times do |i|
140
163
  threads << Thread.new do
141
164
  begin
142
165
  conn = ActiveRecord::Base.connection
@@ -152,7 +175,7 @@ module ActiveRecord
152
175
  end
153
176
  end
154
177
  end
155
- (pool_size - 1).times { threads_ready.pop } # awaits
178
+ (max_pool_size - 1).times { threads_ready.pop } # awaits
156
179
 
157
180
  connection = t1_ready.pop
158
181
  t1_jdbc_connection = connection.jdbc_connection(true)
@@ -189,9 +212,96 @@ module ActiveRecord
189
212
  threads && threads.each(&:join)
190
213
  end
191
214
 
192
- private
215
+ end
193
216
 
194
- def pool_size; @@data_source.max_active end
217
+ class ConnectionPoolWrappingTomcatJdbcDataSourceTest < TestBase
218
+ include ConnectionPoolWrappingDataSourceTestMethods
219
+
220
+ def self.build_data_source(config)
221
+ build_tomcat_jdbc_data_source(config)
222
+ end
223
+
224
+ def self.jndi_name; 'jdbc/TestTomcatJdbcDB' end
225
+
226
+ def self.close_data_source
227
+ @@data_source.send(:close, true) if @@data_source
228
+ end
229
+
230
+ def max_pool_size; @@data_source.max_active end
231
+
232
+ def teardown
233
+ self.class.close_data_source
234
+ end
235
+
236
+ end
237
+
238
+ class ConnectionPoolWrappingC3P0DataSourceTest < TestBase
239
+ include ConnectionPoolWrappingDataSourceTestMethods
240
+
241
+ def self.build_data_source(config)
242
+ build_c3p0_data_source(config)
243
+ end
244
+
245
+ def self.jndi_config
246
+ config = super
247
+ config[:connection_alive_sql] = 'SELECT 1' if old_c3p0?
248
+ config
249
+ end
250
+
251
+ def self.old_c3p0?
252
+ if c3p0_jar = $CLASSPATH.find { |jar| jar =~ /c3p0/ }
253
+ if match = File.basename(c3p0_jar).match(/c3p0\-(.*).jar/)
254
+ return true if match[1] <= '0.9.2.1'
255
+ end
256
+ return false
257
+ end
258
+ nil
259
+ end
260
+
261
+ def test_full_pool_blocks
262
+ return if self.class.old_c3p0?
263
+ super
264
+ end
265
+
266
+ def self.jndi_name; 'jdbc/TestC3P0DB' end
267
+
268
+ def max_pool_size; @@data_source.max_pool_size end
269
+
270
+ def teardown
271
+ # self.class.close_data_source # @@data_source = nil
272
+ self.class.establish_jndi_connection # for next test
273
+ end
274
+
275
+ end
276
+
277
+ class ConnectionPoolWrappingHikariDataSourceTest < TestBase
278
+ include ConnectionPoolWrappingDataSourceTestMethods
279
+
280
+ def self.build_data_source(config)
281
+ data_source = build_hikari_data_source(config)
282
+
283
+ com.zaxxer.hikari.HikariDataSource.class_eval do
284
+ field_reader :pool unless method_defined? :pool
285
+ end
286
+ com.zaxxer.hikari.pool.HikariPool.class_eval do
287
+ field_reader :isShutdown unless method_defined? :isShutdown
288
+ end
289
+
290
+ data_source
291
+ end
292
+
293
+ def self.jndi_name; 'jdbc/TestHikariDB' end
294
+
295
+ def max_pool_size; @@data_source.maximum_pool_size end
296
+
297
+
298
+ def self.close_data_source
299
+ @@data_source.shutdown if @@data_source
300
+ end
301
+
302
+ def teardown
303
+ self.class.establish_jndi_connection # for next test
304
+ end
195
305
 
196
306
  end
197
307
 
@@ -160,7 +160,7 @@ module ActiveRecord
160
160
  def test_does_not_use_more_shared_connections_than_configured_shared_size
161
161
  shared_conn_threads = {}
162
162
  begin
163
- block_connections_in_threads(2) do # only 2 connections left
163
+ block_connections_in_threads(4) do # 6 (out of 10) connections left
164
164
 
165
165
  shared_conn_threads = start_shared_connection_threads(7, :wait)
166
166
 
@@ -170,6 +170,7 @@ module ActiveRecord
170
170
  assert shared_connection?(conn)
171
171
  conn
172
172
  end
173
+
173
174
  assert_equal 5, shared_conns.uniq.size
174
175
 
175
176
  # still one left for normal connections :
@@ -290,7 +291,7 @@ module ActiveRecord
290
291
  protected
291
292
 
292
293
  def current_shared_pool_connection
293
- Thread.current[:shared_pool_connection]
294
+ Thread.current[ connection_pool.send(:shared_connection_key) ]
294
295
  end
295
296
 
296
297
  def set_pool_size(size, shared_size = nil)
@@ -9,8 +9,9 @@ module ActiveRecord
9
9
  def config; AR_CONFIG end
10
10
 
11
11
  def setup
12
- super
12
+ super; require 'active_record/bogacs/pool_support'
13
13
  @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
14
+ @pool.extend Bogacs::PoolSupport # aligns API for AR < 4.0
14
15
  end
15
16
 
16
17
  end
@@ -160,7 +160,11 @@ module ActiveRecord
160
160
  def test_active_connection?
161
161
  assert_false pool.active_connection?
162
162
  assert pool.connection
163
- assert_true pool.active_connection?
163
+ if ActiveRecord::VERSION::MAJOR >= 4
164
+ assert_true pool.active_connection?
165
+ else
166
+ assert pool.active_connection?
167
+ end
164
168
  pool.release_connection
165
169
  assert_false pool.active_connection?
166
170
  end
data/test/test_helper.rb CHANGED
@@ -19,6 +19,7 @@ ENV['DB_POOL_SHARED'] ||= 0.5.to_s
19
19
  # e.g. 40 - ( 40 * 0.75 ) * 5 = 160
20
20
 
21
21
  require 'active_record'
22
+ require 'arjdbc' if defined? JRUBY_VERSION
22
23
 
23
24
  require 'logger'
24
25
  ActiveRecord::Base.logger = Logger.new(STDOUT)
@@ -40,6 +41,7 @@ config[:'password'] = ENV['AR_PASSWORD'] if ENV['AR_PASSWORD']
40
41
  if url = ENV['AR_URL'] || ENV['JDBC_URL']
41
42
  config[:'url'] = url
42
43
  else
44
+ config[:'host'] = ENV['AR_HOST'] || 'localhost'
43
45
  config[:'database'] = ENV['AR_DATABASE'] || 'ar_basin'
44
46
  end
45
47
 
@@ -211,7 +213,9 @@ module ActiveRecord
211
213
 
212
214
  module JndiTestHelper
213
215
 
214
- def setup_jdbc_context
216
+ @@setup_jdbc_context = nil
217
+
218
+ def setup_jdbc_context!
215
219
  load 'test/jars/tomcat-juli.jar'
216
220
  load 'test/jars/tomcat-catalina.jar'
217
221
 
@@ -231,18 +235,23 @@ module ActiveRecord
231
235
  end
232
236
  end
233
237
 
234
- def init_tomcat_jdbc_data_source(ar_jdbc_config = AR_CONFIG)
238
+ def setup_jdbc_context
239
+ @@setup_jdbc_context || setup_jdbc_context!
240
+ @@setup_jdbc_context = true
241
+ end
242
+
243
+ def build_tomcat_jdbc_data_source(ar_jdbc_config = AR_CONFIG)
235
244
  load 'test/jars/tomcat-jdbc.jar'
236
245
 
237
246
  data_source = org.apache.tomcat.jdbc.pool.DataSource.new
238
- configure_dbcp_data_source_attributes(data_source, ar_jdbc_config)
247
+ configure_dbcp_data_source(data_source, ar_jdbc_config)
239
248
 
240
249
  data_source.setJmxEnabled false
241
250
 
242
251
  data_source
243
252
  end
244
253
 
245
- def configure_dbcp_data_source_attributes(data_source, ar_jdbc_config)
254
+ def configure_dbcp_data_source(data_source, ar_jdbc_config)
246
255
  unless driver = ar_jdbc_config[:driver]
247
256
  jdbc_driver_module.load_driver
248
257
  driver = jdbc_driver_module.driver_name
@@ -275,11 +284,125 @@ module ActiveRecord
275
284
  #data_source.setRemoveAbandoned false
276
285
  #data_source.setLogAbandoned true
277
286
  end
278
- private :configure_dbcp_data_source_attributes
287
+ private :configure_dbcp_data_source
288
+
289
+ def build_c3p0_data_source(ar_jdbc_config = AR_CONFIG)
290
+ Dir.glob('test/jars/{c3p0,mchange-commons}*.jar').each { |jar| load jar }
291
+
292
+ data_source = com.mchange.v2.c3p0.ComboPooledDataSource.new
293
+ configure_c3p0_data_source(data_source, ar_jdbc_config)
294
+
295
+ data_source
296
+ end
297
+
298
+ def configure_c3p0_data_source(data_source, ar_jdbc_config)
299
+ unless driver = ar_jdbc_config[:driver]
300
+ jdbc_driver_module.load_driver
301
+ driver = jdbc_driver_module.driver_name
302
+ end
303
+
304
+ data_source.setDriverClass driver
305
+ data_source.setJdbcUrl ar_jdbc_config[:url]
306
+ if user = ar_jdbc_config[:username]
307
+ # data_source.setUser user # WTF C3P0
308
+ data_source.setOverrideDefaultUser user
309
+ end
310
+ data_source.setPassword ar_jdbc_config[:password] if ar_jdbc_config[:password]
311
+
312
+ if ar_jdbc_config[:properties]
313
+ properties = java.util.Properties.new
314
+ properties.putAll ar_jdbc_config[:properties]
315
+ data_source.setProperties properties
316
+ end
317
+ # JDBC pool tunings (some mapped from AR configuration) :
318
+ if ar_jdbc_config[:pool] # default is 100
319
+ data_source.setMaxPoolSize ar_jdbc_config[:pool].to_i
320
+ if prefill = ar_jdbc_config[:pool_prefill]
321
+ data_source.setInitialPoolSize prefill.to_i
322
+ end
323
+ end
324
+ checkout_timeout = ar_jdbc_config[:checkout_timeout] || 5
325
+ data_source.setCheckoutTimeout checkout_timeout * 1000
326
+
327
+ data_source.setAcquireIncrement 1 # default 3
328
+ data_source.setAcquireRetryAttempts 3 # default 30
329
+ data_source.setAcquireRetryDelay 1000 # default 1000
330
+ data_source.setNumHelperThreads 2 # default 3
331
+ end
332
+ private :configure_c3p0_data_source
333
+
334
+
335
+ def build_hikari_data_source(ar_jdbc_config = AR_CONFIG)
336
+ Dir.glob('test/jars/{javassist,slf4j,HikariCP}*.jar').each { |jar| load jar }
337
+
338
+ configure_hikari_data_source(ar_jdbc_config)
339
+ end
340
+
341
+ def configure_hikari_data_source(ar_jdbc_config)
342
+ hikari_config = com.zaxxer.hikari.HikariConfig.new
343
+
344
+ unless driver = ar_jdbc_config[:driver]
345
+ jdbc_driver_module.load_driver
346
+ driver = jdbc_driver_module.driver_name
347
+ end
348
+
349
+ case driver
350
+ when /mysql/i
351
+ hikari_config.setDataSourceClassName 'com.mysql.jdbc.jdbc2.optional.MysqlDataSource'
352
+ hikari_config.addDataSourceProperty 'serverName', ar_jdbc_config[:host] || 'localhost'
353
+ hikari_config.addDataSourceProperty 'databaseName', ar_jdbc_config[:database]
354
+ hikari_config.addDataSourceProperty 'port', ar_jdbc_config[:port] if ar_jdbc_config[:port]
355
+ if true
356
+ hikari_config.addDataSourceProperty 'user', ar_jdbc_config[:username] || 'root'
357
+ end
358
+ if ar_jdbc_config[:password]
359
+ hikari_config.addDataSourceProperty 'password', ar_jdbc_config[:password]
360
+ end
361
+ when /postgres/i
362
+ hikari_config.setDataSourceClassName 'org.postgresql.ds.PGSimpleDataSource'
363
+ hikari_config.addDataSourceProperty 'serverName', ar_jdbc_config[:host] || 'localhost'
364
+ hikari_config.addDataSourceProperty 'databaseName', ar_jdbc_config[:database]
365
+ hikari_config.addDataSourceProperty 'port', ar_jdbc_config[:port] if ar_jdbc_config[:port]
366
+ if ar_jdbc_config[:username]
367
+ hikari_config.addDataSourceProperty 'user', ar_jdbc_config[:username]
368
+ end
369
+ if ar_jdbc_config[:password]
370
+ hikari_config.addDataSourceProperty 'password', ar_jdbc_config[:password]
371
+ end
372
+ else
373
+ hikari_config.setDriverClassName driver
374
+ hikari_config.setJdbcUrl ar_jdbc_config[:url]
375
+ hikari_config.setUsername ar_jdbc_config[:username] if ar_jdbc_config[:username]
376
+ hikari_config.setPassword ar_jdbc_config[:password] if ar_jdbc_config[:password]
377
+ end
378
+
379
+ # TODO: we shall handle raw properties ?!
380
+ #if ar_jdbc_config[:properties]
381
+ # properties = java.util.Properties.new
382
+ # properties.putAll ar_jdbc_config[:properties]
383
+ # hikari_config.setProperties properties
384
+ #end
385
+
386
+ # JDBC pool tunings (some mapped from AR configuration) :
387
+ if ar_jdbc_config[:pool] # default is 100
388
+ hikari_config.setMaximumPoolSize ar_jdbc_config[:pool].to_i
389
+ if prefill = ar_jdbc_config[:pool_prefill]
390
+ hikari_config.setMinConnectionsPerPartition prefill.to_i
391
+ end
392
+ end
393
+
394
+ checkout_timeout = ar_jdbc_config[:checkout_timeout] || 5
395
+ hikari_config.setConnectionTimeout checkout_timeout * 1000
396
+
397
+ hikari_config.setLeakDetectionThreshold 30 * 1000 # default 10s
398
+
399
+ com.zaxxer.hikari.HikariDataSource.new hikari_config
400
+ end
401
+ private :configure_hikari_data_source
279
402
 
280
403
  def bind_data_source(data_source, jndi_name = jndi_config[:jndi])
281
404
  load_driver
282
- javax.naming.InitialContext.new.bind jndi_name, data_source
405
+ javax.naming.InitialContext.new.rebind jndi_name, data_source
283
406
  end
284
407
 
285
408
  def load_driver
@@ -289,6 +412,7 @@ module ActiveRecord
289
412
  def jdbc_driver_module
290
413
  driver = jndi_config[:adapter]
291
414
  driver = 'postgres' if driver == 'postgresql'
415
+ driver = 'mysql' if driver == 'mysql2'
292
416
  require "jdbc/#{driver}"
293
417
  ::Jdbc.const_get ::Jdbc.constants.first
294
418
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-bogacs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karol Bucek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-01 00:00:00.000000000 Z
11
+ date: 2014-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: atomic