activerecord-bogacs 0.1.0 → 0.2.0

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