advanced_connection 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +63 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +218 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.rdoc +3 -0
  10. data/Rakefile +165 -0
  11. data/advanced_connection.gemspec +49 -0
  12. data/gemfiles/jruby/rails4_1.gemfile +7 -0
  13. data/gemfiles/jruby/rails4_2.gemfile +7 -0
  14. data/gemfiles/ruby/rails4_1.gemfile +7 -0
  15. data/gemfiles/ruby/rails4_2.gemfile +7 -0
  16. data/lib/advanced_connection/active_record_ext/abstract_adapter/statement_pooling.rb +121 -0
  17. data/lib/advanced_connection/active_record_ext/abstract_adapter.rb +48 -0
  18. data/lib/advanced_connection/active_record_ext/connection_pool/idle_manager.rb +271 -0
  19. data/lib/advanced_connection/active_record_ext/connection_pool/queues.rb +60 -0
  20. data/lib/advanced_connection/active_record_ext/connection_pool/statement_pooling.rb +42 -0
  21. data/lib/advanced_connection/active_record_ext/connection_pool/without_connection.rb +93 -0
  22. data/lib/advanced_connection/active_record_ext/connection_pool.rb +38 -0
  23. data/lib/advanced_connection/active_record_ext/without_connection.rb +30 -0
  24. data/lib/advanced_connection/active_record_ext.rb +59 -0
  25. data/lib/advanced_connection/config.rb +247 -0
  26. data/lib/advanced_connection/error.rb +27 -0
  27. data/lib/advanced_connection/railtie.rb +40 -0
  28. data/lib/advanced_connection/version.rb +29 -0
  29. data/lib/advanced_connection.rb +65 -0
  30. data/lib/generators/advanced_connection/install/USAGE +5 -0
  31. data/lib/generators/advanced_connection/install/install_generator.rb +30 -0
  32. data/lib/generators/advanced_connection/install/templates/advanced_connection.rb +142 -0
  33. data/lib/tasks/advanced_connection_tasks.rake +25 -0
  34. data/spec/config/database.yml +18 -0
  35. data/spec/config/database.yml.erb +54 -0
  36. data/spec/dummy/.gitignore +1 -0
  37. data/spec/dummy/README.rdoc +28 -0
  38. data/spec/dummy/Rakefile +6 -0
  39. data/spec/dummy/app/assets/images/.keep +0 -0
  40. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  41. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  42. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  43. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  44. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  45. data/spec/dummy/app/mailers/.keep +0 -0
  46. data/spec/dummy/app/models/.keep +0 -0
  47. data/spec/dummy/app/models/book.rb +2 -0
  48. data/spec/dummy/app/models/concerns/.keep +0 -0
  49. data/spec/dummy/app/views/layouts/application.html.erb +13 -0
  50. data/spec/dummy/bin/bundle +3 -0
  51. data/spec/dummy/bin/rails +4 -0
  52. data/spec/dummy/bin/rake +4 -0
  53. data/spec/dummy/bin/setup +29 -0
  54. data/spec/dummy/config/application.rb +32 -0
  55. data/spec/dummy/config/boot.rb +5 -0
  56. data/spec/dummy/config/database.yml +17 -0
  57. data/spec/dummy/config/environment.rb +5 -0
  58. data/spec/dummy/config/environments/development.rb +48 -0
  59. data/spec/dummy/config/environments/production.rb +79 -0
  60. data/spec/dummy/config/environments/test.rb +42 -0
  61. data/spec/dummy/config/initializers/advanced_connection.rb +142 -0
  62. data/spec/dummy/config/initializers/assets.rb +11 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  65. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  66. data/spec/dummy/config/initializers/inflections.rb +16 -0
  67. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  68. data/spec/dummy/config/initializers/session_store.rb +3 -0
  69. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  70. data/spec/dummy/config/locales/en.yml +23 -0
  71. data/spec/dummy/config/routes.rb +56 -0
  72. data/spec/dummy/config/secrets.yml +22 -0
  73. data/spec/dummy/config.ru +4 -0
  74. data/spec/dummy/db/migrate/20160222045238_create_books.rb +10 -0
  75. data/spec/dummy/db/schema.rb +23 -0
  76. data/spec/dummy/lib/assets/.keep +0 -0
  77. data/spec/dummy/log/.keep +0 -0
  78. data/spec/dummy/log/test.log +327 -0
  79. data/spec/dummy/public/404.html +67 -0
  80. data/spec/dummy/public/422.html +67 -0
  81. data/spec/dummy/public/500.html +66 -0
  82. data/spec/dummy/public/favicon.ico +0 -0
  83. data/spec/dummy/test/fixtures/books.yml +9 -0
  84. data/spec/dummy/test/models/book_test.rb +7 -0
  85. data/spec/idle_manager_spec.rb +16 -0
  86. data/spec/spec_helper.rb +25 -0
  87. data/spec/support/db_config.rb +61 -0
  88. metadata +399 -0
@@ -0,0 +1,49 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ # Maintain your gem's version:
4
+ require "advanced_connection/version"
5
+
6
+ # Describe your gem and declare its dependencies:
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "advanced_connection"
9
+ spec.version = AdvancedConnection::VERSION
10
+ spec.authors = ["Carl P. Corliss"]
11
+ spec.email = ["carl.corliss@finalsite.com"]
12
+ spec.homepage = "https://github.com/finalsite/advanced_connection"
13
+ spec.summary = "Provides advanced connection options for rails connection pools"
14
+ spec.description = "Adds idle connection management, statement pooling, and other advanced connection features"
15
+ spec.license = "LGPL"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ # eventually support this:
22
+ # spec.required_engine_version = {
23
+ # :ruby => '~> 2.0',
24
+ # :jruby => '~> 1.7',
25
+ # }
26
+
27
+ spec.add_runtime_dependency "rails", ">= 4.1.0", "< 5.0"
28
+ spec.add_runtime_dependency "activerecord", ">= 4.1.0", "< 5.0"
29
+ spec.add_runtime_dependency "activesupport", ">= 4.1.0", "< 5.0"
30
+
31
+ spec.add_development_dependency "rake", '~> 10.5'
32
+ spec.add_development_dependency "rack", '~> 1.6'
33
+ spec.add_development_dependency "rspec", '~> 3.4'
34
+ spec.add_development_dependency "rspec-its", '~> 1.2'
35
+ spec.add_development_dependency "rspec-collection_matchers", '~> 1.1'
36
+
37
+ # optional dependencies
38
+ if RUBY_ENGINE == 'jruby'
39
+ spec.add_development_dependency "activerecord-jdbcpostgresql-adapter", "~> 1.3"
40
+ else
41
+ spec.add_development_dependency "pg", "~> 0.18"
42
+ spec.add_development_dependency "pry", '~> 0.10'
43
+ spec.add_development_dependency "pry-nav", '~> 0.2'
44
+ end
45
+
46
+ spec.add_development_dependency "guard-rspec", '~> 4.6'
47
+ spec.add_development_dependency "coveralls", '~> 0.8'
48
+ spec.add_development_dependency 'copyright-header', '= 1.0.15'
49
+ end
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activerecord", "~> 4.1.0"
4
+ gem "activesupport", "~> 4.1.0"
5
+ gem "activerecord-jdbcpostgresql-adapter", '~> 1.3.10'
6
+
7
+ gemspec :path => "../../"
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activerecord", "~> 4.2.0"
4
+ gem "activesupport", "~> 4.2.0"
5
+ gem "activerecord-jdbcpostgresql-adapter", '~> 1.3.10'
6
+
7
+ gemspec :path => "../../"
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activerecord", "~> 4.1.0"
4
+ gem "activesupport", "~> 4.1.0"
5
+ gem "pg", "~> 0.18.4"
6
+
7
+ gemspec :path => "../../"
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activerecord", "~> 4.2.0"
4
+ gem "activesupport", "~> 4.2.0"
5
+ gem "pg", "~> 0.18.4"
6
+
7
+ gemspec :path => "../../"
@@ -0,0 +1,121 @@
1
+ #
2
+ # Copyright (C) 2016 Finalsite, LLC
3
+ # Copyright (C) 2016 Carl P. Corliss <carl.corliss@finalsite.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ # this software and associated documentation files (the "Software"), to deal in
7
+ # the Software without restriction, including without limitation the rights to
8
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ # the Software, and to permit persons to whom the Software is furnished to do so,
10
+ # subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ #
22
+ require 'active_record/connection_adapters/abstract_adapter'
23
+
24
+ module AdvancedConnection::ActiveRecordExt
25
+ module AbstractAdapter
26
+ module StatementPooling
27
+ extend ActiveSupport::Concern
28
+
29
+ module ExecuteWrapper
30
+ def __wrap_adapter_exec_methods(*methods)
31
+ Array(methods).flatten.collect(&:to_sym).each { |exec_method|
32
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
33
+ def #{exec_method}_with_callback(sql, *args, &block)
34
+ if Thread.current[:without_callbacks] || sql =~ /^BEGIN/i || transaction_open? || pool.nil?
35
+ #{exec_method}_without_callback(sql, *args, &block)
36
+ else
37
+ run_callbacks(:statement_pooling_connection_checkin) do
38
+ $stderr.puts "#{Thread.current.object_id} executing sql -> \#{sql.inspect}"
39
+ #{exec_method}_without_callback(sql, *args, &block).tap {
40
+ $stderr.puts "#{Thread.current.object_id} Releasing connection..."
41
+ reset!
42
+ pool.release_connection
43
+ $stderr.puts "#{Thread.current.object_id} Connection Released..."
44
+ }
45
+ end
46
+ end
47
+ end
48
+ EOS
49
+ alias_method_chain exec_method, :callback
50
+ }
51
+ end
52
+ alias_method :__wrap_adapter_exec_method, :__wrap_adapter_exec_methods
53
+
54
+ def __wrap_without_callbacks(*methods)
55
+ Array(methods).flatten.collect(&:to_sym).each { |m|
56
+ target, punctuation = m.to_s.sub(/([?!=])$/, ''), $1
57
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
58
+ def #{target}_with_no_callbacks#{punctuation}(*args, &block)
59
+ # $stderr.puts "setting without_callbacks to true"
60
+ Thread.current[:without_callbacks] = true
61
+ #{target}_without_no_callbacks#{punctuation}(*args, &block)
62
+ ensure
63
+ Thread.current[:without_callbacks] = nil
64
+ end
65
+ EOS
66
+ alias_method_chain m, :no_callbacks
67
+ }
68
+ end
69
+ alias_method :__wrap_without_callback, :__wrap_without_callbacks
70
+ end
71
+
72
+ included do
73
+ include ::ActiveSupport::Callbacks
74
+ extend ExecuteWrapper
75
+
76
+ define_callbacks :statement_pooling_connection_checkin
77
+ set_callback :statement_pooling_connection_checkin, :around, :around_connection_checkin
78
+ set_callback :statement_pooling_connection_checkin, :before, :before_connection_checkin
79
+ set_callback :statement_pooling_connection_checkin, :after, :after_connection_checkin
80
+
81
+ DEFAULT_EXEC_METHODS = [ :execute, :exec_no_cache, :query ]
82
+ JDBC_EXEC_METHODS = %w[
83
+ execute
84
+ exec_query
85
+ exec_query_raw
86
+ exec_insert
87
+ exec_update
88
+ exec_delete
89
+ transaction
90
+ ].collect(&:to_sym)
91
+
92
+ if RUBY_ENGINE == 'jruby'
93
+ JDBC_EXEC_METHODS.each { |m| __wrap_adapter_exec_methods m }
94
+ else
95
+ DEFAULT_EXEC_METHODS.each { |m| __wrap_adapter_exec_methods m }
96
+ end
97
+
98
+ [ :active?, :reset!, :disconnect!, :reconnect! ].each { |m|
99
+ __wrap_without_callbacks m
100
+ }
101
+ end
102
+
103
+ def around_connection_checkin(&block)
104
+ callbacks = AdvancedConnection.callbacks.statement_pooling
105
+ if callbacks.around.respond_to? :call
106
+ callbacks.around.call() { block.call }
107
+ end
108
+ end
109
+
110
+ def before_connection_checkin
111
+ callbacks = AdvancedConnection.callbacks.statement_pooling
112
+ callbacks.before.call if callbacks.before.respond_to? :call
113
+ end
114
+
115
+ def after_connection_checkin
116
+ callbacks = AdvancedConnection.callbacks.statement_pooling
117
+ callbacks.after.call if callbacks.after.respond_to? :call
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,48 @@
1
+ #
2
+ # Copyright (C) 2016 Finalsite, LLC
3
+ # Copyright (C) 2016 Carl P. Corliss <carl.corliss@finalsite.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ # this software and associated documentation files (the "Software"), to deal in
7
+ # the Software without restriction, including without limitation the rights to
8
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ # the Software, and to permit persons to whom the Software is furnished to do so,
10
+ # subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ #
22
+ require 'active_record/connection_adapters/abstract_adapter'
23
+
24
+ module AdvancedConnection::ActiveRecordExt
25
+ module AbstractAdapter
26
+ extend ActiveSupport::Autoload
27
+ extend ActiveSupport::Concern
28
+
29
+ eager_autoload do
30
+ autoload :StatementPooling
31
+ end
32
+
33
+ included do
34
+ attr_accessor :last_checked_in, :instantiated_at
35
+ alias_method_chain :initialize, :advanced_connection
36
+ end
37
+
38
+ def initialize_with_advanced_connection(*args, &block)
39
+ @last_checked_in = Time.now - 1.year
40
+ @instantiated_at = Time.now
41
+ initialize_without_advanced_connection(*args, &block)
42
+ end
43
+
44
+ def instance_age
45
+ (Time.now - @instantiated_at).to_f
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,271 @@
1
+ #
2
+ # Copyright (C) 2016 Finalsite, LLC
3
+ # Copyright (C) 2016 Carl P. Corliss <carl.corliss@finalsite.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ # this software and associated documentation files (the "Software"), to deal in
7
+ # the Software without restriction, including without limitation the rights to
8
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ # the Software, and to permit persons to whom the Software is furnished to do so,
10
+ # subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ #
22
+ module AdvancedConnection::ActiveRecordExt
23
+ module ConnectionPool
24
+ module IdleManager
25
+ extend ActiveSupport::Concern
26
+
27
+ included do
28
+ alias_method_chain :initialize, :advanced_connection
29
+ alias_method_chain :checkin, :last_checked_in
30
+
31
+ class IdleManager
32
+ attr_reader :pool, :interval, :thread
33
+
34
+ def initialize(pool, interval)
35
+ @pool = pool
36
+ @interval = interval.to_i
37
+ @thread = nil
38
+ end
39
+
40
+ def run
41
+ return unless interval > 0
42
+
43
+ @thread ||= Thread.new(pool, interval) { |_pool, _interval|
44
+ _pool.send(:idle_info, "starting idle manager; running every #{_interval} seconds")
45
+
46
+ loop do
47
+ sleep _interval
48
+
49
+ begin
50
+ start = Time.now
51
+ _pool.send(:idle_info, "beginning idle connection cleanup")
52
+ _pool.remove_idle_connections
53
+ _pool.send(:idle_info, "beginning idle connection warmup")
54
+ _pool.create_idle_connections
55
+ finish = (Time.now - start).round(6)
56
+ _pool.send(:idle_info, "finished idle connection tasks in #{finish} seconds.; next run in #{pool.max_idle_time} seconds")
57
+ rescue => e
58
+ Rails.logger.error "#{e.class.name}: #{e.message}"
59
+ e.backtrace.each { |line| Rails.logger.error line }
60
+ end
61
+ end
62
+ }
63
+ end
64
+ end
65
+ end
66
+
67
+ def initialize_with_advanced_connection(spec)
68
+ initialize_without_advanced_connection(spec)
69
+
70
+ @available = case queue_type
71
+ when :prefer_older then Queues::OldAgeBiased.new
72
+ when :prefer_younger then Queues::YoungAgeBiased.new
73
+ when :lifo, :stack then Queues::Stack.new
74
+ else
75
+ Rails.logger.warn "Unknown queue_type #{queue_type.inspect} - using standard FIFO instead"
76
+ Queues::FIFO.new
77
+ end
78
+
79
+ @idle_manager = IdleManager.new(self, idle_check_interval).tap { |m| m.run }
80
+ end
81
+
82
+ def queue_type
83
+ @queue_type ||= spec.config.fetch(:queue_type,
84
+ AdvancedConnection.connection_pool_queue_type).to_s.downcase.to_sym
85
+ end
86
+
87
+ def warmup_connection_count
88
+ @warmup_connection_count ||= begin
89
+ conns = spec.config[:warmup_connections] || AdvancedConnection.warmup_connections
90
+ conns.to_i > connection_limit ? connection_limit : conns.to_i
91
+ end
92
+ end
93
+
94
+ def max_idle_time
95
+ @max_idle_time ||= (spec.config[:max_idle_time] || \
96
+ AdvancedConnection.max_idle_time).to_i
97
+ end
98
+
99
+ def idle_check_interval
100
+ @idle_check_interval ||= (spec.config[:idle_check_interval] || \
101
+ AdvancedConnection.idle_check_interval || \
102
+ max_idle_time).to_i
103
+ end
104
+
105
+ def max_idle_connections
106
+ @max_idle_connections ||= begin
107
+ begin
108
+ (spec.config[:max_idle_connections] || \
109
+ AdvancedConnection.max_idle_connections).to_i
110
+ rescue FloatDomainError => e
111
+ raise unless e.message =~ /infinity/i
112
+ ::Float::INFINITY
113
+ end
114
+ end
115
+ end
116
+
117
+ def min_idle_connections
118
+ @min_idle_connections ||= begin
119
+ min_idle = (spec.config[:min_idle_connections] || AdvancedConnection.min_idle_connections).to_i
120
+ min_idle = (min_idle > 0 ? min_idle : 0)
121
+ min_idle <= max_idle_connections ? min_idle : max_idle_connections
122
+ end
123
+ end
124
+
125
+ def connection_limit
126
+ @size
127
+ end
128
+
129
+ def checkin_with_last_checked_in(conn)
130
+ conn.last_checked_in = Time.now
131
+ idle_debug "checking in connection #{conn.object_id} at #{conn.last_checked_in}"
132
+ checkin_without_last_checked_in(conn)
133
+ end
134
+
135
+ def active_connections
136
+ @connections.select(&:in_use?)
137
+ end
138
+
139
+ def available_connections
140
+ @connections.reject(&:in_use?)
141
+ end
142
+
143
+ def idle_connections
144
+ available_connections.select do |conn|
145
+ (Time.now - conn.last_checked_in).to_f > max_idle_time
146
+ end.sort { |a,b|
147
+ case queue_type
148
+ when :prefer_younger then
149
+ # when prefering younger, we sort oldest->youngest
150
+ # this ensures that older connections will be culled
151
+ # during #remove_idle_connections()
152
+ -(a.instance_age <=> b.instance_age)
153
+ when :prefer_older then
154
+ # when prefering older, we sort youngest->oldest
155
+ # this ensures that younger connections will be culled
156
+ # during #remove_idle_connections()
157
+ (a.instance_age <=> b.instance_age)
158
+ else
159
+ # with fifo / lifo queues, we only care about the
160
+ # last time a given connection was used (inferred
161
+ # by when it was last checked into the pool).
162
+ # This ensures that the longer idling connections
163
+ # will be culled.
164
+ -(a.last_checked_in <=> b.last_checked_in)
165
+ end
166
+ }
167
+ end
168
+
169
+ def pool_statistics
170
+ idle = active = available = 0
171
+ synchronize do
172
+ idle = idle_connections.size
173
+ active = active_connections.size
174
+ available = available_connections.size
175
+ end
176
+ total = active + available
177
+
178
+ ActiveSupport::OrderedOptions.new.merge({
179
+ total: total, idle: idle, active: active, available: available,
180
+ })
181
+ end
182
+
183
+ def warmup_connections(count = nil)
184
+ count ||= warmup_connection_count
185
+ slots = connection_limit - @connections.size
186
+ count = slots if slots < count
187
+
188
+ if slots >= count
189
+ idle_info "Warming up #{count} connection#{count > 1 ? 's' : ''}"
190
+ synchronize do
191
+ count.times {
192
+ conn = checkout_new_connection
193
+ @available.add conn
194
+ }
195
+ end
196
+ end
197
+ end
198
+
199
+ def create_idle_connections
200
+ idle_count = idle_connections.size
201
+ open_slots = connection_limit - @connections.size
202
+
203
+ # if we already have enough idle connections, do nothing
204
+ return unless idle_count < min_idle_connections
205
+
206
+ # if we don't have enough available slots (i.e., current pool size
207
+ # is greater than max pool size) then do nothing
208
+ return unless open_slots > 0
209
+
210
+ # otherwise, spin up connections up to our min_idle_connections setting
211
+ create_count = min_idle_connections - idle_count
212
+ create_count = open_slots if create_count > open_slots
213
+
214
+ warmup_connections(create_count)
215
+ end
216
+
217
+ def remove_idle_connections
218
+ # don't attempt to remove idle connections if we have threads waiting
219
+ return if @available.num_waiting > 0
220
+
221
+ idle_conns = idle_connections
222
+ idle_count = idle_conns.size
223
+
224
+ if idle_count > max_idle_connections
225
+ cull_count = (idle_count - max_idle_connections)
226
+
227
+ culled = 0
228
+ idle_conns.each_with_index do |conn, idx|
229
+ last_ci = (Time.now - conn.last_checked_in).to_f
230
+ if idx < cull_count
231
+ culled += remove_connection(conn) ? 1 : 0
232
+ idle_info "culled connection ##{idx} id##{conn.object_id} - age:#{conn.instance_age} last_checkin:#{last_ci}"
233
+ else
234
+ idle_info "kept connection ##{idx} id##{conn.object_id} - age:#{conn.instance_age} last_checkin:#{last_ci}"
235
+ end
236
+ end
237
+
238
+ idle_info "culled %d connections" % culled
239
+ end
240
+ end
241
+
242
+ private
243
+
244
+ def remove_connection(conn)
245
+ synchronize do
246
+ return false if conn.in_use?
247
+ remove conn
248
+ conn.disconnect!
249
+ end
250
+ true
251
+ end
252
+
253
+ def idle_message(format, *args)
254
+ stats = pool_statistics
255
+ format(
256
+ "IdleManager (Actv:%d,Avail:%d,Idle:%d,Total:%d): #{format}",
257
+ stats.active, stats.available, stats.idle, stats.total,
258
+ *args
259
+ )
260
+ end
261
+
262
+ def idle_debug(format, *args)
263
+ Rails.logger.debug idle_message(format, *args)
264
+ end
265
+
266
+ def idle_info(format, *args)
267
+ Rails.logger.info idle_message(format, *args)
268
+ end
269
+ end
270
+ end
271
+ end