advanced_connection 0.5.1

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.
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